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,196 @@
# -*- coding: utf-8 -*- #
# Copyright 2018 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.
"""Abstract base class for concepts.
This base class cannot be used as a concept on its own but must be subclassed,
and the methods Attribute(), GetPresentationName(), and Parse() must be
implemented.
Example usage:
class IntConcept(Concept):
def __init__(self, **kwargs):
super(IntConcept, self).__init__(name='int', **kwargs)
def Attribute(self):
return Attribute(concept=self,
fallthroughs=self.fallthroughs,
help=self.BuildHelpText(),
required=self.required,
hidden=self.hidden,
completer=self.completer,
metavar=self.metavar)
def GetPresentationName(self):
return googlecloudsdk.command_lib.concepts.names.FlagNameFormat(
self.name)
def BuildHelpText(self):
super(IntConcept, self).BuildHelpText() + ' Must be an int.'
def Parse(self, dependencies):
try:
return int(dependencies.value)
except ValueError:
raise googlecloudsdk.command_lib.concepts.exceptions.Error(
'Could not parse int concept; you provided [{}]'
.format(dependencies.value))
* Note for Concept Implementers *
When implementing a new concept that produces a single argument, you can
subclass googlecloudsdk.command_lib.concepts.all_concepts.SimpleArg in order to
take advantage of general functionality, such as creating a simple presentation
name based on whether concept.positional is True, determining whether the
produced attribute is required, raising an exception if no value is found and
the concept is required, and storing fallthroughs.
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import abc
import six
class Concept(six.with_metaclass(abc.ABCMeta, object)):
"""Abstract base class for concept args.
Attributes:
name: str, the name of the concept. Used to determine
the argument or group name of the concept.
key: str, the name by which the parsed concept is stored in the dependency
view. If not given, is the same as the concept's name. Generally,
this should only be set and used by containing concepts when parsing
from a DependencyView object. End users of concepts do not need to
use it.
help_text: str, the help text to be displayed for this concept.
required: bool, whether the concept must be provided by the end user. If
False, it's acceptable to have an empty result; otherwise, an empty
result will raise an error.
hidden: bool, whether the associated argument or group should be hidden.
"""
def __init__(self, name, key=None, help_text='', required=False,
hidden=False):
self.name = name
self.help_text = help_text
self.required = required
self.hidden = hidden
self.key = key or self.name
@abc.abstractmethod
def Attribute(self):
"""Returns an Attribute object representing the attributes.
Must be defined in subclasses.
Returns:
Attribute | AttributeGroup, the attribute or group.
"""
raise NotImplementedError
@abc.abstractmethod
def GetPresentationName(self):
"""Returns the main name for the concept."""
raise NotImplementedError
def BuildHelpText(self):
"""Builds and returns the help text.
Returns:
str, the help text for the concept.
"""
return self.help_text
def Marshal(self):
"""Returns the list of concepts that this concept marshals."""
return None
@abc.abstractmethod
def Parse(self, dependencies):
"""Parses the concept.
Args:
dependencies: a DependenciesView object.
Returns:
the parsed concept.
Raises:
googlecloudsdk.command_lib.concepts.exceptions.Error, if parsing fails.
"""
raise NotImplementedError
@abc.abstractmethod
def IsArgRequired(self):
"""Returns whether this concept is required to be specified by argparse."""
return False
def MakeArgKwargs(self):
"""Returns argparse kwargs shared between all concept types."""
return {
'help': self.BuildHelpText(),
'required': self.IsArgRequired(),
'hidden': self.hidden,
}
class Attribute(object):
"""An attribute that gets transformed to an argument.
Attributes:
concept: Concept, the underlying concept.
key: str, the name by which the Attribute is looked up in the dependency
view.
fallthroughs: [deps.Fallthrough], the list of fallthroughs for the concept.
kwargs: {str: any}, other metadata describing the attribute. Available
keys include: required (bool), hidden (bool), help (str), completer,
default, nargs. **Note: This is currently used essentially as a
passthrough to the argparse library.
"""
def __init__(self, concept=None, fallthroughs=None, **kwargs):
self.concept = concept
self.fallthroughs = fallthroughs or []
self.kwargs = kwargs
@property
def arg_name(self):
"""A string property representing the final argument name."""
return self.concept.GetPresentationName()
class AttributeGroup(object):
"""Represents an object that gets transformed to an argument group.
Attributes:
concept: Concept, the underlying concept.
key: str, the name by which the Attribute is looked up in the dependency
view.
attributes: [Attribute | AttributeGroup], the list of attributes or
attribute groups contained in this attribute group.
kwargs: {str: any}, other metadata describing the attribute. Available
keys include: required (bool), mutex (bool), hidden (bool), help (str).
**Note: This is currently used essentially as a passthrough to the
argparse library.
"""
def __init__(self, concept=None, attributes=None, **kwargs):
self.concept = concept
self.key = concept.key
self.attributes = attributes or []
self.kwargs = kwargs

View File

@@ -0,0 +1,168 @@
# -*- coding: utf-8 -*- #
# Copyright 2018 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.
"""Classes that manage concepts and dependencies.
For usage examples, see
googlecloudsdk/command_lib/concepts/all_concepts/base.py.
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.command_lib.concepts import base
from googlecloudsdk.command_lib.concepts import dependency_managers
from googlecloudsdk.command_lib.concepts import names
import six
class ConceptManager(object):
"""A manager that contains all concepts (v2) for a given command.
This object is responsible for registering all concepts, creating arguments
for the concepts, and creating a RuntimeParser which will be responsible
for parsing the concepts.
Attributes:
concepts: [base.Concept], a list of concepts.
runtime_parser: RuntimeParser, the runtime parser manager for all concepts.
"""
def __init__(self):
self.concepts = []
self.runtime_parser = None
self._command_level_fallthroughs = {}
def AddConcept(self, concept):
"""Add a single concept.
This method adds a concept to the ConceptManager. It does not immediately
have any effect on the command's argparse parser.
Args:
concept: base.Concept, an instantiated concept.
"""
self.concepts.append(concept)
def AddToParser(self, parser):
"""Adds concept arguments and concept RuntimeParser to argparse parser.
For each concept, the Attribute() method is called, and all resulting
attributes and attribute groups are translated into arguments for the
argparse parser.
Additionally, a concept-specific RuntimeParser is created with all of the
resulting attributes from the first step. (This will be responsible for
parsing the concepts.) It is registered to the argparse parser, and will
appear in the final parsed namespace under CONCEPT_ARGS.
Args:
parser: googlecloudsdk.calliope.parser_arguments.ArgumentInterceptor, the
argparse parser to which to add argparse arguments.
"""
attributes = [concept.Attribute() for concept in self.concepts]
self._AddToArgparse(attributes, parser)
self.runtime_parser = RuntimeParser(attributes)
parser.add_concepts(self.runtime_parser)
def _AddToArgparse(self, attributes, parser):
"""Recursively add an arg definition to the parser."""
for attribute in attributes:
if isinstance(attribute, base.Attribute):
parser.add_argument(attribute.arg_name, **attribute.kwargs)
continue
group = parser.add_argument_group(attribute.kwargs.pop('help'),
**attribute.kwargs)
self._AddToArgparse(attribute.attributes, group)
class RuntimeParser(object):
"""An object to manage parsing all concepts via their attributes.
Once argument parsing is complete and ParseConcepts is called, each parsed
concept is stored on this runtime parser as an attribute, named after the
name of that concept.
Attributes:
parsed_args: the argparse namespace after arguments have been parsed.
<CONCEPT_NAME> (the namespace format of each top level concept, such as
"foo_bar"): the parsed concept corresponding to that name.
"""
def __init__(self, attributes):
self.parsed_args = None
self._attributes = {}
for attribute in attributes:
attr_name = names.ConvertToNamespaceName(
attribute.concept.GetPresentationName())
if attr_name in self._attributes:
raise ValueError('Attempting to add two concepts with the same '
'presentation name: [{}]'.format(attr_name))
self._attributes[attr_name] = attribute
def ParseConcepts(self):
"""Parse all concepts.
Stores the result of parsing concepts, keyed to the namespace format of
their presentation name. Afterward, will be accessible as
args.<LOWER_SNAKE_CASE_NAME>.
Raises:
googlecloudsdk.command_lib.concepts.exceptions.Error: if parsing fails.
"""
# Collect all final parse values in final before assigning back to args
# because multiple FinalParse calls may use an attr_name multiple times.
final = {}
for attr_name, attribute in six.iteritems(self._attributes):
dependencies = dependency_managers.DependencyNode.FromAttribute(attribute)
final[attr_name] = FinalParse(dependencies, self.ParsedArgs)
# Set the final parsed name=value in the args namespace. add_argument(),
# either called explicitly, or via the concept manager, detects duplicate
# names and raises an exception before this method is called.
for name, value in six.iteritems(final):
setattr(self.parsed_args, name, value)
def ParsedArgs(self):
"""A lazy property to use during concept parsing.
Returns:
googlecloudsdk.calliope.parser_extensions.Namespace: the parsed argparse
namespace | None, if the parser hasn't been registered to the namespace
yet.
"""
return self.parsed_args
def FinalParse(dependencies, arg_getter):
"""Lazy parser stored under args.CONCEPT_ARGS.
Args:
dependencies: dependency_managers.DependencyNode, the root of the tree of
the concept's dependencies.
arg_getter: Callable, a function that returns the parsed args namespace.
Raises:
googlecloudsdk.command_lib.concepts.exceptions.Error: if parsing fails.
Returns:
the result of parsing the root concept.
"""
dependency_manager = dependency_managers.DependencyManager(dependencies)
parsed_args = arg_getter()
return dependency_manager.ParseConcept(parsed_args)

View File

@@ -0,0 +1,221 @@
# -*- coding: utf-8 -*- #
# Copyright 2018 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.
"""Classes that manage concepts and dependencies."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import functools
from googlecloudsdk.calliope.concepts import deps as deps_lib
from googlecloudsdk.command_lib.concepts import base
from googlecloudsdk.command_lib.concepts import exceptions
from googlecloudsdk.command_lib.concepts import names
import six
def GetPresentationNames(nodes):
return (child.GetPresentationName() for child in nodes)
class DependencyManager(object):
"""Holds dependency info for a single overall concept and creates views.
Attributes:
node: the DependencyNode at the root of the dependency tree for this
concept.
"""
def __init__(self, node):
self.node = node
def ParseConcept(self, parsed_args):
"""Parse the concept recursively by building the dependencies in a DFS.
Args are formatted in the same way as usage_text.py:GetArgsUsage, except
concepts in a concept group are not sorted. Concepts are displayed in the
order they were added to the group.
Args:
parsed_args: the raw parsed argparse namespace.
Raises:
googlecloudsdk.command_lib.concepts.exceptions.Error: if parsing fails.
Returns:
the parsed top-level concept.
"""
def _ParseConcept(node):
"""Recursive parsing."""
if not node.is_group:
fallthroughs = []
if node.arg_name:
fallthroughs.append(deps_lib.ArgFallthrough(node.arg_name))
fallthroughs += node.fallthroughs
return node.concept.Parse(
DependencyViewFromValue(
functools.partial(
deps_lib.GetFromFallthroughs, fallthroughs, parsed_args),
marshalled_dependencies=node.dependencies))
# TODO(b/120132521) Replace and eliminate argparse extensions
also_optional = [] # The optional concepts that were not specified.
have_optional = [] # The specified optional (not required) concepts.
have_required = [] # The specified required concepts.
need_required = [] # The required concepts that must be specified.
namespace = {}
for name, child in six.iteritems(node.dependencies):
result = None
try:
result = _ParseConcept(child)
if result:
if child.concept.required:
have_required.append(child.concept)
else:
have_optional.append(child.concept)
else:
also_optional.append(child.concept)
except exceptions.MissingRequiredArgumentError:
need_required.append(child.concept)
namespace[name] = result
if need_required:
missing = ' '.join(GetPresentationNames(need_required))
if have_optional or have_required:
specified_parts = []
if have_required:
specified_parts.append(' '.join(
GetPresentationNames(have_required)))
if have_required and have_optional:
specified_parts.append(':')
if have_optional:
specified_parts.append(' '.join(
GetPresentationNames(have_optional)))
specified = ' '.join(specified_parts)
if have_required and have_optional:
if node.concept.required:
specified = '({})'.format(specified)
else:
specified = '[{}]'.format(specified)
raise exceptions.ModalGroupError(
node.concept.GetPresentationName(), specified, missing)
count = len(have_required) + len(have_optional)
if node.concept.mutex:
specified = ' | '.join(
GetPresentationNames(node.concept.concepts))
if node.concept.required:
specified = '({specified})'.format(specified=specified)
if count != 1:
raise exceptions.RequiredMutexGroupError(
node.concept.GetPresentationName(), specified)
else:
if count > 1:
raise exceptions.OptionalMutexGroupError(
node.concept.GetPresentationName(), specified)
return node.concept.Parse(DependencyView(namespace))
return _ParseConcept(self.node)
class DependencyView(object):
"""Simple namespace used by concept.Parse for concept groups."""
def __init__(self, values_dict):
for key, value in six.iteritems(values_dict):
setattr(self, names.ConvertToNamespaceName(key), value)
class DependencyViewFromValue(object):
"""Simple namespace for single value."""
def __init__(self, value_getter, marshalled_dependencies=None):
self._value_getter = value_getter
self._marshalled_dependencies = marshalled_dependencies
@property
def value(self):
"""Lazy value getter.
Returns:
the value of the attribute, from its fallthroughs.
Raises:
deps_lib.AttributeNotFoundError: if the value cannot be found.
"""
try:
return self._value_getter()
except TypeError:
return self._value_getter
@property
def marshalled_dependencies(self):
"""Returns the marshalled dependencies or None if not marshalled."""
return self._marshalled_dependencies
class DependencyNode(object):
"""A node of a dependency tree.
Attributes:
name: the name that will be used to look up the dependency from higher
in the tree. Corresponds to the "key" of the attribute.
concept: the concept of the attribute.
dependencies: {str: DependencyNode}, a map from dependency names to
sub-dependency trees.
arg_name: str, the argument name of the attribute.
fallthroughs: [deps_lib._Fallthrough], the list of fallthroughs for the
dependency.
marshalled: [base.Concept], the list of concepts marshalled by concept.
The marshalled dependencies are generated here, but concept handles the
parsing.
"""
def __init__(self, name, is_group, concept=None, dependencies=None,
arg_name=None, fallthroughs=None):
self.name = name
self.is_group = is_group
self.concept = concept
self.dependencies = dependencies
self.arg_name = arg_name
self.fallthroughs = fallthroughs or []
@classmethod
def FromAttribute(cls, attribute):
"""Builds the dependency tree from the attribute."""
kwargs = {
'concept': attribute.concept,
}
marshal = attribute.concept.Marshal()
if marshal:
attributes = [concept.Attribute() for concept in marshal]
elif not isinstance(attribute, base.Attribute):
attributes = attribute.attributes
else:
attributes = None
if isinstance(attribute, base.Attribute) and (marshal or not attributes):
kwargs['arg_name'] = attribute.arg_name
kwargs['fallthroughs'] = attribute.fallthroughs
if attributes:
kwargs['dependencies'] = {a.concept.key: DependencyNode.FromAttribute(a)
for a in attributes}
return DependencyNode(attribute.concept.key,
not isinstance(attribute, base.Attribute), **kwargs)

View File

@@ -0,0 +1,90 @@
# -*- coding: utf-8 -*- #
# Copyright 2018 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.
"""Exceptions for concept args."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.core import exceptions
class Error(exceptions.Error):
"""Base class for errors in this module."""
class ConstraintError(Error):
"""Error when converting a constraint."""
def __init__(self, concept_name, kind, string, message):
super(ConstraintError, self).__init__(
'Invalid {} [{}] for [{}]. {}'.format(
kind, string, concept_name, message))
class ParseError(Error):
"""Error when parsing a concept."""
def __init__(self, concept_name, message):
super(ParseError, self).__init__(
'Failed to parse [{}]. {}'.format(concept_name, message))
class ValidationError(Error):
"""Error when validating a concept."""
def __init__(self, concept_name, message):
super(ValidationError, self).__init__(
'Failed to validate [{}]. {}'.format(concept_name, message))
class InitializationError(Error):
"""Error when a concept was initialized with an invalid value."""
class MissingRequiredArgumentError(Error):
"""Error when a required concept can't be found."""
def __init__(self, concept_name, message):
super(MissingRequiredArgumentError, self).__init__(
'No value was provided for [{}]: {}'.format(concept_name, message))
class ModalGroupError(Error):
"""Error when a modal group was not specified correctly."""
def __init__(self, concept_name, specified, missing):
super(ModalGroupError, self).__init__(
'Failed to specify [{}]: '
'{specified}: {missing} must be specified.'
.format(concept_name, specified=specified, missing=missing))
class OptionalMutexGroupError(Error):
"""Error when an optional mutex group was not specified correctly."""
def __init__(self, concept_name, conflict):
super(OptionalMutexGroupError, self).__init__(
'Failed to specify [{}]: At most one of {conflict} can be specified.'
.format(concept_name, conflict=conflict))
class RequiredMutexGroupError(Error):
"""Error when a required mutex group was not specified correctly."""
def __init__(self, concept_name, conflict):
super(RequiredMutexGroupError, self).__init__(
'Failed to specify [{}]: Exactly one of {conflict} must be specified.'
.format(concept_name, conflict=conflict))

View File

@@ -0,0 +1,52 @@
# -*- coding: utf-8 -*- #
# Copyright 2018 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.
"""Helpers for naming concepts and attributes."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
def StripFlagPrefix(name):
"""Strip the flag prefix from a name, if present."""
if name.startswith('--'):
return name[2:]
return name
def AddFlagPrefix(name):
"""Add the flag prefix to a name, if not present."""
if name.startswith('--'):
return name
# Does not guarantee a valid flag name.
return '--' + name
def ConvertToFlagName(name):
"""Convert name to flag format (e.g. '--foo-bar')."""
return AddFlagPrefix(name).lower().replace('_', '-').replace(' ', '-')
def ConvertToNamespaceName(name):
"""Convert name to namespace format (e.g. 'foo_bar')."""
name = StripFlagPrefix(name)
return name.lower().replace('-', '_').replace(' ', '_')
def ConvertToPositionalName(name):
"""Convert name to positional format (e.g. 'FOO_BAR')."""
name = StripFlagPrefix(name)
return name.upper().replace('-', '_').replace(' ', '_')