# -*- coding: utf-8 -*- # # Copyright 2022 Google LLC. All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Shared utility functions for Cloud SCC commands.""" from __future__ import absolute_import from __future__ import division from __future__ import print_function from __future__ import unicode_literals import re from googlecloudsdk.command_lib.scc import errors from googlecloudsdk.core import properties def GetFindingsParentFromPositionalArguments(args): """Converts user input to one of: organization, project, or folder.""" id_pattern = re.compile("[0-9]+") parent = None if hasattr(args, "parent"): if not args.parent: parent = properties.VALUES.scc.parent.Get() else: parent = args.parent if parent is None: # Use organization property as backup for legacy behavior. parent = properties.VALUES.scc.organization.Get() organization = ( getattr(args, "organization", None) if hasattr(args, "organization") else None ) project = getattr(args, "project", None) if hasattr(args, "project") else None folder = getattr(args, "folder", None) if hasattr(args, "folder") else None if organization: parent = ( f"organizations/{organization}" if id_pattern.match(organization) else organization ) elif project: parent = f"projects/{project}" if id_pattern.match(project) else project elif folder: parent = f"folders/{folder}" if id_pattern.match(folder) else folder if parent is None: raise errors.InvalidSCCInputError( "Could not find Parent argument. Please provide the parent argument." ) if id_pattern.match(parent): # Prepend organizations/ if only number value is provided. parent = "organizations/" + parent if not ( parent.startswith("organizations/") or parent.startswith("projects/") or parent.startswith("folders/") ): error_message = ( "Parent must match either [0-9]+, organizations/[0-9]+, " "projects/.* " "or folders/.*." "" ) raise errors.InvalidSCCInputError(error_message) return parent def GetParentFromPositionalArguments(args): """Converts user input to one of: organization, project, or folder.""" id_pattern = re.compile("[0-9]+") parent = None if hasattr(args, "parent"): if not args.parent: parent = properties.VALUES.scc.parent.Get() else: parent = args.parent if parent is None: # Use organization property as backup for legacy behavior. parent = properties.VALUES.scc.organization.Get() organization = ( getattr(args, "organization", None) if hasattr(args, "organization") else None ) project = getattr(args, "project", None) if hasattr(args, "project") else None folder = getattr(args, "folder", None) if hasattr(args, "folder") else None if organization: parent = ( f"organizations/{organization}" if id_pattern.match(organization) else organization ) elif project: parent = f"projects/{project}" if id_pattern.match(project) else project elif folder: parent = f"folders/{folder}" if id_pattern.match(folder) else folder if parent is None and hasattr(args, "project"): parent = args.project if parent is None and hasattr(args, "folder"): parent = args.folder if parent is None: raise errors.InvalidSCCInputError( "Could not find Parent argument. Please provide the parent argument." ) if id_pattern.match(parent): # Prepend organizations/ if only number value is provided. parent = "organizations/" + parent if not ( parent.startswith("organizations/") or parent.startswith("projects/") or parent.startswith("folders/") ): error_message = ( "Parent must match either [0-9]+, organizations/[0-9]+, " "projects/.* " "or folders/.*." "" ) raise errors.InvalidSCCInputError(error_message) return parent def GetParentFromNamedArguments(args): """Gets and validates parent from named arguments.""" if args.organization is not None: if "/" in args.organization: pattern = re.compile("^organizations/[0-9]{1,19}$") if not pattern.match(args.organization): raise errors.InvalidSCCInputError( "When providing a full resource path, it must include the pattern " "'^organizations/[0-9]{1,19}$'." ) else: return args.organization else: pattern = re.compile("^[0-9]{1,19}$") if not pattern.match(args.organization): raise errors.InvalidSCCInputError( "Organization does not match the pattern '^[0-9]{1,19}$'." ) else: return "organizations/" + args.organization if hasattr(args, "folder") and args.folder is not None: if "/" in args.folder: pattern = re.compile("^folders/.*$") if not pattern.match(args.folder): raise errors.InvalidSCCInputError( "When providing a full resource path, it must include the pattern " "'^folders/.*$'." ) else: return args.folder else: return "folders/" + args.folder if hasattr(args, "project") and args.project is not None: if "/" in args.project: pattern = re.compile("^projects/.*$") if not pattern.match(args.project): raise errors.InvalidSCCInputError( "When providing a full resource path, it must include the pattern " "'^projects/.*$'." ) else: return args.project else: return "projects/" + args.project def CleanUpUserMaskInput(mask): """Removes spaces from a field mask provided by user.""" return mask.replace(" ", "") def IsLocationSpecified(args, resource_name): """Returns true if location is specified.""" location_in_resource_name = "/locations/" in resource_name # Validate mutex on location. if args.IsKnownAndSpecified("location") and location_in_resource_name: raise errors.InvalidSCCInputError( "Only provide location in a full resource name " "or in a --location flag, not both." ) return args.IsKnownAndSpecified("location") or location_in_resource_name def GetVersionFromArguments( args, resource_name="", deprecated_args=None, version_specific_existing_resource: bool = False, ): """Returns the correct version to call based on the user supplied arguments. Args: args: arguments resource_name: (optional) resource name e.g. finding, mute_config deprecated_args: (optional) list of deprecated arguments for a command version_specific_existing_resource: (optional) command is invoked on a resource which is not interoperable between versions. Returns: Version of securitycenter api to handle command, either "v1" or "v2" """ location_specified = IsLocationSpecified(args, resource_name) # Non-interoperable resources such as BigQuery Export and NotificationConfigs # may only be accessed through the version in which they were instantiated. # This may be determined by the presence of a location. if version_specific_existing_resource: if location_specified: return "v2" else: return "v1" # Args that have been deprecated in v2 may necessitate a v1 call. if deprecated_args: for argument in deprecated_args: if args.IsKnownAndSpecified(argument) and location_specified: raise errors.InvalidSCCInputError( "Location is not available when deprecated arguments are used" ) if args.IsKnownAndSpecified(argument) and not location_specified: return "v1" if args.api_version == "v1": if location_specified: return "v2" return "v1" return "v2" def ValidateAndGetLocation(args, version): """Validates --location flag input and returns location.""" if version == "v2": if args.location is not None: # Validate location if a user wants to use v2 and specifes a location. name_pattern = re.compile("^locations/[A-Za-z0-9-]{0,61}$") id_pattern = re.compile("^[A-Za-z0-9-]{0,61}$") if name_pattern.match(args.location): return args.location.split("/")[1] if id_pattern.match(args.location): return args.location raise errors.InvalidSCCInputError( "location does not match the pattern" " '^locations/[A-Za-z0-9-]{0,61}$'. or [A-Za-z0-9-]{0,61}" ) # Return the default location (global) if version is equal to v1. return args.location