feat: Add new gcloud commands, API clients, and third-party libraries across various services.

This commit is contained in:
2026-01-01 20:26:35 +01:00
parent 5e23cbece0
commit a19e592eb7
25221 changed files with 8324611 additions and 0 deletions

View File

@@ -0,0 +1,142 @@
# -*- coding: utf-8 -*- #
# Copyright 2023 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.
"""Management API gcloud constants."""
from __future__ import annotations
import dataclasses
from typing import Dict, List
# TODO: b/308433842 - This can be deleted once gcloud python migration to
# 3.12 is complete
# pylint: disable=g-importing-member, g-import-not-at-top, g-bad-import-order
# pyformat: disable
import sys
if sys.version_info >= (3, 11):
from enum import StrEnum
else:
# in 3.11+, using the below class in an f-string would put the enum
# name instead of its value
from enum import Enum
class StrEnum(str, Enum):
pass
# pyformat: enable
# pylint: enable=g-importing-member, g-import-not-at-top, g-bad-import-order
# DELETE UP TO HERE
class CustomModuleType(StrEnum):
SHA = 'securityHealthAnalyticsCustomModules'
ETD = 'eventThreatDetectionCustomModules'
EFFECTIVE_ETD = 'effectiveEventThreatDetectionCustomModules'
EFFECTIVE_SHA = 'effectiveSecurityHealthAnalyticsCustomModules'
BILLING_METADATA = 'billingMetadata'
SERVICE_RESOURCE_PLURAL_NAME = 'securityCenterServices'
@dataclasses.dataclass(frozen=True)
class SecurityCenterService:
"""Dataclass that reprsesents a Security Center Service."""
name: str
abbreviation: str | None = None
def __str__(self) -> str:
if self.abbreviation is not None:
return f'{self.name} (can be abbreviated as {self.abbreviation})'
else:
return self.name
def __eq__(self, other: 'SecurityCenterService') -> bool:
if isinstance(other, SecurityCenterService):
is_same_name = self.name == other.name
is_same_abbreviation = (
self.abbreviation == other.abbreviation
and self.abbreviation is not None
)
return is_same_name or is_same_abbreviation
else:
return False
def make_service_inventory(
services: List[SecurityCenterService],
) -> Dict[str, SecurityCenterService]:
"""Maps a list of SecurityCenterService objects to an immutable dictionary.
The dictionary will contain a mapping between each service name and service
object as well as service abbreviation to service object if the service has
an abbreviation.
Args:
services: list of service objects to add to the dictionary.
Returns:
an immutable dictionary mapping service names and abbreviations to services.
Raises:
KeyError: if there are duplicate entries for any service name or
abbreviation.
"""
for i in range(len(services)):
for j in range(i + 1, len(services)):
if services[i] == services[j]:
raise KeyError(
f'Duplicate entries in service inventory: {services[i]} at index'
f' {i} and {services[j]} at index {j} in service inventory. Both'
' service names and abbreviations must be unique.'
)
abbreviated_services = [
service for service in services if service.abbreviation is not None
]
names_to_services = {service.name: service for service in services}
abbreviations_to_services = {
service.abbreviation: service for service in abbreviated_services
}
return {**names_to_services, **abbreviations_to_services}
SUPPORTED_SERVICES = (
SecurityCenterService('security-health-analytics', abbreviation='sha'),
SecurityCenterService('event-threat-detection', abbreviation='etd'),
SecurityCenterService('container-threat-detection', abbreviation='ctd'),
SecurityCenterService('vm-threat-detection', abbreviation='vmtd'),
SecurityCenterService('web-security-scanner', abbreviation='wss'),
SecurityCenterService('vm-threat-detection-aws', abbreviation='vmtd-aws'),
SecurityCenterService('cloud-run-threat-detection', abbreviation='crtd'),
SecurityCenterService('vm-manager', abbreviation='vmm'),
SecurityCenterService(
'ec2-vulnerability-assessment', abbreviation='ec2-va'
),
SecurityCenterService(
'gce-vulnerability-assessment', abbreviation='gce-va'
),
SecurityCenterService(
'azure-vulnerability-assessment', abbreviation='azure-va'
),
SecurityCenterService('notebook-security-scanner', abbreviation='nss'),
SecurityCenterService('agent-engine-threat-detection', abbreviation='aetd'),
)
SERVICE_INVENTORY: Dict[str, SecurityCenterService] = make_service_inventory(
SUPPORTED_SERVICES
)

View File

@@ -0,0 +1,126 @@
# -*- coding: utf-8 -*- #
# Copyright 2023 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.
"""Management API gcloud errors."""
from googlecloudsdk.command_lib.scc.manage import constants
from googlecloudsdk.core import exceptions
from googlecloudsdk.core import resources
class Error(exceptions.Error):
"""Base error for this module."""
class InvalidParentError(Error):
"""An error representing an invalid CRM parent."""
def __init__(self, bad_parent_arg: str):
super(Error, self).__init__(
f'"{bad_parent_arg}" is not a valid parent. The parent name should'
' begin with "organizations/", "projects/", or "folders/".'
)
class InvalidParentFlagError(Error):
"""An error representing an invalid CRM parent."""
def __init__(self, bad_parent_arg: str):
super().__init__(
f'"{bad_parent_arg}" is not a valid parent. The parent name should'
' begin with "organizations/"or "projects/".'
)
class InvalidServiceNameError(Error):
"""An error representing an invalid service name."""
def __init__(self, bad_service_name_arg: str):
valid_service_names = '\n\t\t'.join(
[str(service) for service in constants.SUPPORTED_SERVICES]
)
super(Error, self).__init__(
f"'{bad_service_name_arg}' is not a valid service name.\n\n\tThe"
f' service name must be one of:\n\t\t{valid_service_names}\n'
)
class MissingCustomModuleNameOrIdError(Error):
"""An error representing a missing custom module name or id."""
def __init__(self):
super(Error, self).__init__('Missing custom module name or ID.')
class InvalidCustomModuleIdError(Error):
"""An error representing a custom module ID that does not conform to _CUSTOM_MODULE_ID_REGEX."""
def __init__(self, bad_module_id_arg: str):
if bad_module_id_arg is None:
super(Error, self).__init__('Missing custom module ID.')
else:
super(Error, self).__init__(
f'"{bad_module_id_arg}" is not a valid custom module ID. The ID'
' should consist only of numbers and be 1-20 characters in length.'
)
class InvalidCustomModuleNameError(Error):
"""An error representing an invalid custom module name."""
def __init__(self, bad_module_name_arg: str, module_type: str):
valid_formats = '\n\n\t\t'.join(_GetValidNameFormatForModule(module_type))
super(Error, self).__init__(
f'"{bad_module_name_arg}" is not a valid custom module name.\n\n\tThe'
f' expected format is one of:\n\n\t\t{valid_formats}\n'
)
def _GetValidNameFormatForModule(
module_type: constants.CustomModuleType,
) -> str:
"""Returns a list of name format strings for the given module_type."""
collections = [
f'securitycentermanagement.organizations.locations.{module_type}',
f'securitycentermanagement.projects.locations.{module_type}',
f'securitycentermanagement.folders.locations.{module_type}',
]
return [
resources.REGISTRY.GetCollectionInfo(collection).GetPath('')
for collection in collections
]
class InvalidCustomConfigFileError(Error):
"""Error if a custom config file is improperly formatted."""
class InvalidResourceFileError(Error):
"""Error if a test data file is improperly formatted."""
class InvalidConfigValueFileError(Error):
"""Error if a config value file is improperly formatted."""
class InvalidUpdateMaskInputError(Error):
"""Error if neither a custom configuration or an enablement state were given to update."""
class InvalidEnablementStateError(Error):
"""Error if an enablement state is anything but ENABLED or DISABLED."""

View File

@@ -0,0 +1,327 @@
# -*- coding: utf-8 -*- #
# Copyright 2023 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.
"""Specify common flags for management gcloud."""
import textwrap
from googlecloudsdk.calliope import arg_parsers
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.resource_manager import completers
from googlecloudsdk.command_lib.scc.manage import constants
def CreateParentFlag(
resource_name: str = 'custom module', required=False
) -> base.Argument:
"""Returns a flag for capturing an org, project, or folder name.
The flag can be provided in one of 2 forms:
1. --parent=organizations/<id>, --parent=projects/<id or name>,
--parent=folders/<id>
2. One of:
* --organizations=<id> or organizations/<id>
* --projects=<id or name> or projects/<id or name>
* --folders=<id> or folders/<id>
Args:
resource_name: The name of the resource for which the flag is created. The
default value is set to 'custom module'.
required: whether or not this flag is required
"""
root = base.ArgumentGroup(mutex=True, required=required)
root.AddArgument(
base.Argument(
'--parent',
required=False,
help=textwrap.dedent(
"""Parent associated with the {}. Can be one of
organizations/<id>, projects/<id or name>, folders/<id>""".format(
resource_name
)
),
)
)
root.AddArgument(
base.Argument(
'--organization',
required=False,
metavar='ORGANIZATION_ID',
completer=completers.OrganizationCompleter,
help='Organization associated with the {}.'.format(resource_name),
)
)
root.AddArgument(
base.Argument(
'--project',
required=False,
metavar='PROJECT_ID_OR_NUMBER',
completer=completers.ProjectCompleter,
help='Project associated with the {}.'.format(resource_name),
)
)
root.AddArgument(
base.Argument(
'--folder',
required=False,
metavar='FOLDER_ID',
help='Folder associated with the {}.'.format(resource_name),
)
)
return root
def CreateModuleIdOrNameArg(
module_type: constants.CustomModuleType,
) -> base.Argument:
"""A positional argument representing a custom module ID or name."""
return base.Argument(
'module_id_or_name',
help="""The custom module ID or name. The expected format is {parent}/[locations/global]/MODULE_TYPE/{module_id} or just {module_id}. Where module_id is a numeric identifier 1-20 characters
in length. Parent is of the form organizations/{id}, projects/{id or name},
folders/{id}.""".replace(
'MODULE_TYPE', module_type
),
)
def CreateCustomConfigFlag(required=True) -> base.Argument:
return base.Argument(
'--custom-config-from-file',
required=required,
metavar='CUSTOM_CONFIG',
help="""Path to a YAML custom configuration file.""",
type=arg_parsers.FileContents(),
)
def CreateTestResourceFlag(required=True) -> base.Argument:
return base.Argument(
'--resource-from-file',
required=required,
metavar='TEST_DATA',
help="""Path to a YAML file that contains the resource data to validate the Security Health Analytics custom module against.""",
type=arg_parsers.FileContents(),
)
def CreateModuleTypeFlag(required=True) -> base.Argument:
return base.Argument(
'--module-type',
required=required,
metavar='MODULE_TYPE',
help="""Type of the custom module. For a list of valid module types please visit https://cloud.google.com/security-command-center/docs/custom-modules-etd-overview#custom_modules_and_templates.""",
)
def CreateValidateOnlyFlag(required=False) -> base.Argument:
return base.Argument(
'--validate-only',
required=required,
default=None,
action='store_true',
help="""If present, the request is validated (including IAM checks) but no action is taken.""",
)
def CreateUpdateFlags(
module_type: constants.CustomModuleType,
file_type,
required=True,
) -> base.Argument:
"""Returns a custom-config flag or an enablement-state flag, or both."""
root = base.ArgumentGroup(mutex=False, required=required)
root.AddArgument(
base.Argument(
'--custom-config-file',
required=False,
default=None,
help="""Path to a {} file that contains the custom config to set for the module.""".format(
file_type
),
type=arg_parsers.FileContents(),
)
)
root.AddArgument(
CreateEnablementStateFlag(required=False, module_type=module_type)
)
return root
def CreateEnablementStateFlag(
module_type: constants.CustomModuleType,
required: bool,
):
"""Creates an enablement state flag."""
if module_type == constants.CustomModuleType.SHA:
module_name = 'Security Health Analytics'
elif module_type == constants.CustomModuleType.ETD:
module_name = 'Event Threat Detection'
return base.Argument(
'--enablement-state',
required=required,
default=None,
help="""Sets the enablement state of the {} custom module. Valid options are ENABLED, DISABLED, OR INHERITED.""".format(
module_name
),
)
def CreateEtdCustomConfigFilePathFlag(required=True) -> base.Argument:
return base.Argument(
'--custom-config-file',
required=required,
metavar='CUSTOM_CONFIG',
help="""Path to a JSON custom configuration file of the ETD custom module.""",
type=arg_parsers.FileContents(),
)
def CreateDisplayNameFlag(required=True) -> base.Argument:
return base.Argument(
'--display-name',
required=required,
metavar='DISPLAY-NAME',
help="""The display name of the custom module.""",
)
def CreateServiceNameArg() -> base.Argument:
"""A positional argument representing the service name."""
valid_service_names = '\n\n* '.join(
[str(service) for service in constants.SUPPORTED_SERVICES]
)
return base.Argument(
'service_name',
help=(
'The service name, provided either in lowercase hyphenated form'
' (e.g. security-health-analytics), or in abbreviated form (e.g.'
' sha) if applicable.\n\nThe list of supported services is:\n\n*'
f' {valid_service_names}'
),
)
def CreateServiceUpdateFlags(
file_type: str,
required: bool = True,
) -> base.Argument:
"""Returns a module-config flag or an enablement-state flag, or both."""
root = base.ArgumentGroup(mutex=False, required=required)
root.AddArgument(
base.Argument(
'--module-config-file',
required=False,
default=None,
help=(
f'Path to a {file_type} file that contains the module config to'
' set for the given module and service.'
),
type=arg_parsers.FileContents(),
)
)
root.AddArgument(CreateServiceEnablementStateFlag(required=False))
return root
def CreateServiceEnablementStateFlag(
required: bool,
):
"""Creates a service enablement state flag."""
return base.Argument(
'--enablement-state',
required=required,
default=None,
help="""Sets the enablement state of the Security Center service.
Valid options are ENABLED, DISABLED, OR INHERITED. The INHERITED
state is only valid when setting the enablement state at the project or folder level.""",
)
def CreateModuleList() -> base.Argument:
"""An optional argument representing a comma separated list of module names."""
return base.Argument(
'--filter-modules',
help="""If provided, only prints module information for modules specified
in the list. Provided as a comma separated list of module names in
SCREAMING_SNAKE_CASE format (e.g. WEB_UI_ENABLED, API_KEY_NOT_ROTATED).
A single module name is also valid.""",
)
def CreateFlagForParent(
resource_name: str = 'billing metadata', required=False
) -> base.Argument:
"""Returns a flag for capturing an org, project name.
The flag can be provided in one of 2 forms:
1. --parent=organizations/<id>, --parent=projects/<id or name>
2. One of:
* --organizations=<id> or organizations/<id>
* --projects=<id or name> or projects/<id or name>
Args:
resource_name: The name of the resource for which the flag is created. The
default value is set to 'billing metadata'.
required: whether or not this flag is required
Returns:
A base.Argument object.
"""
root = base.ArgumentGroup(mutex=True, required=required)
root.AddArgument(
base.Argument(
'--parent',
required=False,
help=textwrap.dedent(
"""Parent associated with the {}. Can be one of
organizations/<id>, projects/<id or name>""".format(
resource_name
)
),
)
)
root.AddArgument(
base.Argument(
'--organization',
required=False,
metavar='ORGANIZATION_ID',
completer=completers.OrganizationCompleter,
help='Organization associated with the {}.'.format(resource_name),
)
)
root.AddArgument(
base.Argument(
'--project',
required=False,
metavar='PROJECT_ID_OR_NUMBER',
completer=completers.ProjectCompleter,
help='Project associated with the {}.'.format(resource_name),
)
)
return root

View File

@@ -0,0 +1,425 @@
# -*- coding: utf-8 -*- #
# Copyright 2023 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.
"""Common flag parsing for management gcloud."""
import json
import re
from apitools.base.py import encoding
from googlecloudsdk.api_lib.resource_manager import folders
from googlecloudsdk.command_lib.scc.manage import constants
from googlecloudsdk.command_lib.scc.manage import errors
from googlecloudsdk.core import properties
from googlecloudsdk.core import resources
from googlecloudsdk.core import yaml
from googlecloudsdk.generated_clients.apis.securitycentermanagement.v1 import securitycentermanagement_v1_messages as messages
_CUSTOM_MODULE_ID_REGEX = re.compile('[0-9]{1,20}')
def GetParentResourceNameFromArgs(args) -> str:
"""Returns the relative path to the parent from args.
Args:
args: command line args.
Returns:
The relative path. e.g. 'projects/foo/locations/global',
'folders/1234/locations/global'.
"""
if args.parent:
return f'{_ParseParent(args.parent).RelativeName()}/locations/global'
return f'{_GetParentResourceFromArgs(args).RelativeName()}/locations/global'
def _GetParentResourceFromArgs(args):
if args.organization:
return resources.REGISTRY.Parse(
args.organization, collection='cloudresourcemanager.organizations'
)
elif args.folder:
return folders.FoldersRegistry().Parse(
args.folder, collection='cloudresourcemanager.folders'
)
else:
return resources.REGISTRY.Parse(
args.project or properties.VALUES.core.project.Get(required=True),
collection='cloudresourcemanager.projects',
)
def GetServiceNameFromArgs(args) -> str:
"""Returns the specified service name from args if it exists.
Otherwise, an exception is raised detailing the parsing error along with the
expectation.
Args:
args: The argument input as the gcloud command.
Raises:
InvalidServiceNameError: the specified service name was invalid.
"""
parent = GetParentResourceNameFromArgs(args)
maybe_service_name_or_abbr = args.service_name.lower()
service = constants.SERVICE_INVENTORY.get(maybe_service_name_or_abbr)
if service:
return f'{parent}/{constants.SERVICE_RESOURCE_PLURAL_NAME}/{service.name}'
else:
raise errors.InvalidServiceNameError(args.service_name)
def GetModuleIdFromArgs(args) -> str:
"""Returns the module id from args."""
if not args.module_id_or_name:
raise errors.InvalidCustomModuleIdError(None)
match = _CUSTOM_MODULE_ID_REGEX.fullmatch(args.module_id_or_name)
if match:
return match[0]
else:
raise errors.InvalidCustomModuleIdError(args.module_id_or_name)
def GetModuleNameFromArgs(args, module_type: constants.CustomModuleType) -> str:
"""Returns the specified module name from args if it exists.
Otherwise, an exception is raised detailing the parsing error along with the
expectation.
Args:
args: the args
module_type: the module type (see
googlecloudsdk.command_lib.scc.manage.constants)
Raises:
MissingCustomModuleNameOrIdError: no module name or id was specified.
InvalidCustomModuleNameError: the specified module name was invalid.
InvalidCustomModuleIdError: the specified module id was invalid.
"""
if not args.module_id_or_name:
raise errors.MissingCustomModuleNameOrIdError()
# First try to see if we can parse a resource name
collections = [
f'securitycentermanagement.organizations.locations.{module_type}',
f'securitycentermanagement.projects.locations.{module_type}',
f'securitycentermanagement.folders.locations.{module_type}',
]
is_possible_resource_name = (
_IsPossibleResourceName(args.module_id_or_name)
or len(args.GetSpecifiedArgNames()) == 1
)
for collection in collections:
try:
return resources.REGISTRY.Parse(
args.module_id_or_name, collection=collection
).RelativeName()
except resources.RequiredFieldOmittedException:
pass
if is_possible_resource_name:
# The error messages provided by the default gcloud parsing are awful so we
# detect a resource name misformatting here and print a better error
raise errors.InvalidCustomModuleNameError(
args.module_id_or_name, module_type
)
parent = GetParentResourceNameFromArgs(args)
module_id = GetModuleIdFromArgs(args)
return f'{parent}/{module_type}/{module_id}'
def _ParseParent(parent: str) -> str:
"""Extracts parent name from a string of the form {organizations|projects|folders}/<id>."""
if parent.startswith('organizations/'):
return resources.REGISTRY.Parse(
parent, collection='cloudresourcemanager.organizations'
)
elif parent.startswith('folders/'):
return folders.FoldersRegistry().Parse(
parent, collection='cloudresourcemanager.folders'
)
elif parent.startswith('projects/'):
return resources.REGISTRY.Parse(
parent,
collection='cloudresourcemanager.projects',
)
else:
raise errors.InvalidParentError(parent)
def _IsPossibleResourceName(name: str) -> bool:
return (
name.startswith('organizations')
or name.startswith('projects')
or name.startswith('folders')
)
def GetCustomConfigFromArgs(file):
"""Process the custom config file for the custom module."""
if file is not None:
try:
config_dict = yaml.load(file)
return encoding.DictToMessage(config_dict, messages.CustomConfig)
except yaml.YAMLParseError as ype:
raise errors.InvalidCustomConfigFileError(
'Error parsing custom config file [{}]'.format(ype)
)
def GetTestResourceFromArgs(file):
"""Process the test resource data file for the custom module to test against."""
try:
resource_dict = yaml.load(file)
return encoding.DictToMessage(resource_dict, messages.SimulatedResource)
except yaml.YAMLParseError as ype:
raise errors.InvalidResourceFileError(
'Error parsing resource file [{}]'.format(ype)
)
def GetConfigValueFromArgs(file):
"""Process the config custom file for the custom module."""
if file is not None:
try:
config = json.loads(file)
return encoding.DictToMessage(
config, messages.EventThreatDetectionCustomModule.ConfigValue
)
except json.JSONDecodeError as e:
raise errors.InvalidConfigValueFileError(
'Error parsing config value file [{}]'.format(e)
)
else:
return None
def ParseJSONFile(file):
"""Converts the contents of a JSON file into a string."""
if file is not None:
try:
config = json.loads(file)
return json.dumps(config)
except json.JSONDecodeError as e:
raise errors.InvalidConfigValueFileError(
'Error parsing config value file [{}]'.format(e)
)
else:
return None
def GetEnablementStateFromArgs(
enablement_state: str,
module_type: constants.CustomModuleType
):
"""Parse the enablement state."""
if module_type == constants.CustomModuleType.SHA:
state_enum = (
messages.SecurityHealthAnalyticsCustomModule.EnablementStateValueValuesEnum
)
elif module_type == constants.CustomModuleType.ETD:
state_enum = (
messages.EventThreatDetectionCustomModule.EnablementStateValueValuesEnum
)
else:
raise errors.InvalidModuleTypeError(
f'Module type "{module_type}" is not a valid module type.'
)
if enablement_state is None:
raise errors.InvalidEnablementStateError(
'Error parsing enablement state. Enablement state cannot be empty.'
)
state = enablement_state.upper()
if state == 'ENABLED':
return state_enum.ENABLED
elif state == 'DISABLED':
return state_enum.DISABLED
elif state == 'INHERITED':
return state_enum.INHERITED
else:
raise errors.InvalidEnablementStateError(
f'Error parsing enablement state. "{state}" is not a valid enablement'
' state. Please provide one of ENABLED, DISABLED, or INHERITED.'
)
def CreateUpdateMaskFromArgs(args):
"""Create an update mask with the args given."""
if args.enablement_state is not None and args.custom_config_file is not None:
return 'enablement_state,custom_config'
elif args.enablement_state is not None:
return 'enablement_state'
elif args.custom_config_file is not None:
return 'custom_config'
else:
raise errors.InvalidUpdateMaskInputError(
'Error parsing Update Mask. Either a custom configuration or an'
' enablement state (or both) must be provided to update the custom'
' module.'
)
def GetModuleConfigValueFromArgs(file: str):
"""Process the module config file for the service."""
if file is not None:
try:
config = yaml.load(file)
return encoding.DictToMessage(
config, messages.SecurityCenterService.ModulesValue
)
except (yaml.YAMLParseError, AttributeError) as ype:
raise errors.InvalidConfigValueFileError(
f'Error parsing config value file [{ype}]'
)
else:
return None
def GetServiceEnablementStateFromArgs(enablement_state: str):
"""Parse the service enablement state."""
state_enum = (
messages.SecurityCenterService.IntendedEnablementStateValueValuesEnum
)
if enablement_state is None:
return None
state = enablement_state.upper()
if state == 'ENABLED':
return state_enum.ENABLED
elif state == 'DISABLED':
return state_enum.DISABLED
elif state == 'INHERITED':
return state_enum.INHERITED
else:
raise errors.InvalidEnablementStateError(
f'Error parsing enablement state. "{state}" is not a valid enablement'
' state. Please provide one of ENABLED, DISABLED, or INHERITED.'
)
def CreateUpdateMaskFromArgsForService(args):
"""Create an update mask with the args given for the given service."""
if args.enablement_state is not None and args.module_config_file is not None:
return 'intended_enablement_state,modules'
elif args.enablement_state is not None:
return 'intended_enablement_state'
elif args.module_config_file is not None:
return 'modules'
else:
raise errors.InvalidUpdateMaskInputError(
'Error parsing Update Mask. Either a module configuration or an'
' enablement state (or both) must be provided to update the service.'
)
def GetModuleListFromArgs(args) -> {str}:
"""Returns a list of module names from args."""
if not args.filter_modules:
return []
modules = args.filter_modules.strip('[]')
modules_list = modules.split(',')
modules_set = {module.strip() for module in modules_list}
return modules_set
def GetModuleNamePathFromArgs(
args, module_type: constants.CustomModuleType
) -> str:
"""Returns the specified module name path from args if it exists.
Args:
args: command line args.
module_type: the module type (see
googlecloudsdk.command_lib.scc.manage.constants)
Returns:
The relative path. e.g.
'organizations/1234/locations/global/{module_type}',
'projects/foo/locations/global/{module_type}'.
"""
if args.parent:
return (
f'{_ParseParentFlag(args.parent).RelativeName()}/locations/global/'
f'{module_type}'
)
return (
f'{_GetParentResourceFromArg(args).RelativeName()}/locations/global/'
f'{module_type}'
)
def _ParseParentFlag(parent: str) -> str:
"""Extracts parent name from {organizations|projects}/<id>.
Args:
parent: The parent string to parse.
Returns:
The relative path of the parent.
Raises:
InvalidParentFlagError: The provided parent string is invalid.
"""
if parent.startswith('organizations/'):
return resources.REGISTRY.Parse(
parent, collection='cloudresourcemanager.organizations'
)
if parent.startswith('projects/'):
return resources.REGISTRY.Parse(
parent,
collection='cloudresourcemanager.projects',
)
raise errors.InvalidParentFlagError(parent)
def _GetParentResourceFromArg(args):
"""Returns the parent resource from the given args.
Args:
args: command line args.
Returns:
The parent resource.
"""
if args.organization:
return resources.REGISTRY.Parse(
args.organization, collection='cloudresourcemanager.organizations'
)
return resources.REGISTRY.Parse(
args.project or properties.VALUES.core.project.Get(required=True),
collection='cloudresourcemanager.projects',
)