File: //snap/google-cloud-cli/396/lib/googlecloudsdk/calliope/concepts/concepts.py
# -*- coding: utf-8 -*- #
# Copyright 2017 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 to specify concept and resource specs.
Concept specs hold information about concepts. "Concepts" are any entity that
has multiple attributes, which can be specified via multiple flags on the
command line. A single concept spec should be created and re-used for the same
concept everywhere it appears.
Resource specs (currently the only type of concept spec used in gcloud) hold
information about a Cloud resource. "Resources" are types of concepts that
correspond to Cloud resources specified by a collection path, such as
'example.projects.shelves.books'. Their attributes correspond to the parameters
of their collection path. As with concept specs, a single resource spec
should be defined and re-used for each collection.
For resources, attributes can be configured by ResourceParameterAttributeConfigs
using kwargs. In many cases, users should also be able to reuse configs for the
same attribute across several resources (for example,
'example.projects.shelves.books.pages' could also use the shelf and project
attribute configs).
"""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import abc
import re
from googlecloudsdk.calliope.concepts import deps as deps_lib
from googlecloudsdk.calliope.concepts import deps_map_util
from googlecloudsdk.calliope.concepts import util as format_util
from googlecloudsdk.command_lib.util.apis import yaml_command_schema_util as util
from googlecloudsdk.core import exceptions
from googlecloudsdk.core import properties
from googlecloudsdk.core import resources
IGNORED_FIELDS = {
'project': 'project',
'projectId': 'project',
'projectsId': 'project',
}
class Error(exceptions.Error):
"""Base class for errors in this module."""
class InitializationError(Error):
"""Raised if a spec fails to initialize."""
class ResourceConfigurationError(Error):
"""Raised if a resource is improperly declared."""
class InvalidResourceArgumentLists(Error):
"""Exception for missing, extra, or out of order arguments."""
def __init__(self, expected, actual):
expected = ['[' + e + ']' if e in IGNORED_FIELDS else e for e in expected]
super(InvalidResourceArgumentLists, self).__init__(
'Invalid resource arguments: Expected [{}], Found [{}].'.format(
', '.join(expected), ', '.join(actual)))
class ConceptSpec(object, metaclass=abc.ABCMeta):
"""Base class for concept args."""
@property
@abc.abstractmethod
def attributes(self):
"""A list of Attribute objects representing the attributes of the concept.
"""
@property
@abc.abstractmethod
def name(self):
"""The name of the overall concept."""
@abc.abstractmethod
def IsAnchor(self, attribute):
"""Returns True if attribute is an anchor."""
@abc.abstractmethod
def IsLeafAnchor(self, attribute):
"""Returns True if attribute is a leaf anchor."""
@abc.abstractmethod
def Initialize(self, fallthroughs_map, parsed_args=None):
"""Initializes the concept using fallthroughs and parsed args."""
@abc.abstractmethod
def Parse(self, attribute_to_args_map, base_fallthroughs_map,
parsed_args=None, plural=False, allow_empty=False):
"""Lazy parsing function for resource."""
@abc.abstractmethod
def BuildFullFallthroughsMap(
self, attribute_to_args_map, base_fallthroughs_map):
"""Builds list of fallthroughs for each attribute."""
def __eq__(self, other):
if not isinstance(other, type(self)):
return False
else:
return self.name == other.name and self.attributes == other.attributes
def __hash__(self):
return hash(self.name) + hash(self.attributes)
class _Attribute(object):
"""A base class for concept attributes.
Attributes:
name: The name of the attribute. Used primarily to control the arg or flag
name corresponding to the attribute. Must be in all lower case.
param_name: corresponds to where the attribute is mapped in the resource
help_text: String describing the attribute's relationship to the concept,
used to generate help for an attribute flag.
required: True if the attribute is required.
fallthroughs: [googlecloudsdk.calliope.concepts.deps_lib.Fallthrough], the
list of sources of data, in priority order, that can provide a value for
the attribute if not given on the command line. These should only be
sources inherent to the attribute, such as associated properties, not
command-specific sources.
completer: core.cache.completion_cache.Completer, the completer associated
with the attribute.
value_type: the type to be accepted by the attribute arg. Defaults to str.
"""
def __init__(self, name, param_name, help_text=None, required=False,
fallthroughs=None, completer=None, value_type=None):
"""Initializes."""
# Check for attributes that mix lower- and uppercase. Camel case is not
# handled consistently among libraries.
if re.search(r'[A-Z]', name) and re.search('r[a-z]', name):
raise ValueError(
'Invalid attribute name [{}]: Attribute names should be in lower '
'snake case (foo_bar) so they can be transformed to flag names.'
.format(name))
self.name = name
self.param_name = param_name or name
self.help_text = help_text
self.required = required
self.fallthroughs = fallthroughs or []
self.completer = completer
self.value_type = value_type or str
def __eq__(self, other):
"""Overrides."""
if not isinstance(other, type(self)):
return False
return (self.name == other.name and self.param_name == other.param_name
and self.help_text == other.help_text
and self.required == other.required
and self.completer == other.completer
and self.fallthroughs == other.fallthroughs
and self.value_type == other.value_type)
def __hash__(self):
return sum(map(hash, [
self.name, self.param_name, self.help_text, self.required,
self.completer, self.value_type])) + sum(map(hash, self.fallthroughs))
class Attribute(_Attribute):
"""An attribute of a resource.
Has all attributes of the base class along with resource-specific attributes.
Attributes:
completion_request_params: {str: str}, a dict of field names to params to
use as static field values in any request to complete this resource.
completion_id_field: str, the ID field of the return value in the
response for completion requests.
"""
def __init__(self, name, completion_request_params=None,
completion_id_field=None, **kwargs):
"""Initializes."""
self.completion_request_params = completion_request_params or {}
self.completion_id_field = completion_id_field
super(Attribute, self).__init__(name, **kwargs)
def __eq__(self, other):
"""Overrides."""
return (super(Attribute, self).__eq__(other)
and self.completer == other.completer
and self.completion_request_params
== other.completion_request_params
and self.completion_id_field == other.completion_id_field)
def __hash__(self):
return super(Attribute, self).__hash__() + sum(
map(hash, [str(self.completer),
str(self.completion_request_params),
self.completion_id_field]))
class ResourceSpec(ConceptSpec):
"""Defines a Cloud resource as a set of attributes for argument creation.
"""
disable_auto_complete = True
@classmethod
def FromYaml(cls, yaml_data, is_positional=None, api_version=None):
"""Constructs an instance of ResourceSpec from yaml data.
Args:
yaml_data: dict, the parsed data from a resources.yaml file under
command_lib/.
is_positional: bool, optional value that determines if anchor argument is
a positional and reformats anchor attribute name accordingly.
api_version: string, overrides the default version in the resource
registry if provided.
Returns:
A ResourceSpec object.
"""
# pylint: disable=g-import-not-at-top
from googlecloudsdk.command_lib.util.apis import registry
# pylint: enable=g-import-not-at-top
collection = registry.GetAPICollection(
yaml_data['collection'], api_version=api_version)
attributes = ParseAttributesFromData(
yaml_data.get('attributes'), collection.detailed_params)
return cls(
resource_collection=collection.full_name,
resource_name=yaml_data['name'],
api_version=collection.api_version,
disable_auto_completers=yaml_data.get(
'disable_auto_completers', ResourceSpec.disable_auto_complete),
plural_name=yaml_data.get('plural_name'),
is_positional=is_positional,
**{attribute.parameter_name: attribute for attribute in attributes})
def __init__(self, resource_collection, resource_name='resource',
api_version=None, disable_auto_completers=disable_auto_complete,
plural_name=None, is_positional=None, **kwargs):
"""Initializes a ResourceSpec.
To use a ResourceSpec, give a collection path such as
'cloudiot.projects.locations.registries', and optionally an
API version.
For each parameter in the collection path, an attribute is added to the
resource spec. Names can be created by default or overridden in the
attribute_configs dict, which maps from the parameter name to a
ResourceParameterAttributeConfig object. ResourceParameterAttributeConfigs
also contain information about the help text that describes the attribute.
Attribute naming: By default, attributes are named after their collection
path param names, or "name" if they are the "anchor" attribute (the final
parameter in the path).
Args:
resource_collection: The collection path of the resource.
resource_name: The name of the resource, which will be used in attribute
help text. Defaults to 'resource'.
api_version: Overrides the default version in the resource
registry.
disable_auto_completers: bool, whether to add completers automatically
where possible.
plural_name: str, the pluralized name. Will be pluralized by default rules
if not given in cases where the resource is referred to in the plural.
is_positional: bool, optional value that determines if anchor argument is
a positional and reformats anchor attribute name accordingly.
**kwargs: Parameter names (such as 'projectsId') from the
collection path, mapped to ResourceParameterAttributeConfigs.
Raises:
ResourceConfigurationError: if the resource is given unknown params or the
collection has no params.
"""
self._name = resource_name
self.plural_name = plural_name
self.collection = resource_collection
self._resources = resources.REGISTRY.Clone()
self._collection_info = self._resources.GetCollectionInfo(
resource_collection, api_version=api_version)
self.disable_auto_completers = disable_auto_completers
collection_params = self._collection_info.GetParams('')
self._attributes = []
self._param_names_map = {}
orig_kwargs = list(kwargs.keys())
# Add attributes.
anchor = False
for i, param_name in enumerate(collection_params):
if i == len(collection_params) - 1:
anchor = True
attribute_config = kwargs.pop(param_name,
ResourceParameterAttributeConfig())
attribute_name = self._AttributeName(param_name, attribute_config,
anchor=anchor,
is_positional=is_positional)
new_attribute = Attribute(
name=attribute_name,
param_name=param_name,
help_text=attribute_config.help_text,
required=True,
fallthroughs=attribute_config.fallthroughs,
completer=attribute_config.completer,
value_type=attribute_config.value_type,
completion_request_params=attribute_config.completion_request_params,
completion_id_field=attribute_config.completion_id_field)
self._attributes.append(new_attribute)
# Keep a map from attribute names to param names. While attribute names
# are used for error messaging and arg creation/parsing, resource parsing
# during command runtime requires parameter names.
self._param_names_map[new_attribute.name] = param_name
if not self._attributes:
raise ResourceConfigurationError('Resource [{}] has no parameters; no '
'arguments will be generated'.format(
self._name))
if kwargs:
raise ResourceConfigurationError('Resource [{}] was given an attribute '
'config for unknown attribute(s): '
'Expected [{}], Found [{}]'
.format(self._name,
', '.join(collection_params),
', '.join(orig_kwargs)))
@property
def attributes(self):
return self._attributes
@property
def name(self):
return self._name
@property
def anchor(self):
"""The "anchor" attribute of the resource."""
# self.attributes cannot be empty; will cause an error on init.
return self.attributes[-1]
def IsAnchor(self, attribute):
"""Convenience method."""
return attribute == self.anchor
def IsLeafAnchor(self, attribute):
"""Convenience method."""
return self.IsAnchor(attribute)
@property
def attribute_to_params_map(self):
"""A map from all attribute names to param names."""
return self._param_names_map
@property
def collection_info(self):
return self._collection_info
# TODO(b/314193603): Add ParamName, AttributeName, and
# attribute_to_params_map to multitype to enable resource completers.
# Then add items to the meta class.
def ParamName(self, attribute_name):
"""Gets the param name from attribute. Used for autocompleters."""
if attribute_name not in self.attribute_to_params_map:
raise ValueError(
'No param name found for attribute [{}]. Existing attributes are '
'[{}]'.format(attribute_name,
', '.join(sorted(self.attribute_to_params_map.keys()))))
return self.attribute_to_params_map[attribute_name]
def AttributeName(self, param_name):
"""Gets the attribute name from param name. Used for autocompleters."""
for attribute_name, p in self.attribute_to_params_map.items():
if p == param_name:
return attribute_name
else:
return None
def Initialize(self, fallthroughs_map, parsed_args=None):
"""Initializes a resource given its fallthroughs.
The fallthrough map is used to derive each resource attribute (including
the anchor). Returns a fully parsed resource object.
Args:
fallthroughs_map: {str: [deps_lib._FallthroughBase]}, a dict of finalized
fallthroughs for the resource.
parsed_args: the argparse namespace.
Returns:
(googlecloudsdk.core.resources.Resource) the fully initialized resource.
Raises:
googlecloudsdk.calliope.concepts.concepts.InitializationError, if the
concept can't be initialized.
"""
params = {}
# Returns a function that can be used to parse each attribute, which will be
# used only if the resource parser does not receive a fully qualified
# resource name.
def LazyGet(name):
return lambda: deps_lib.Get(name, fallthroughs_map, parsed_args)
for attribute in self.attributes:
params[attribute.param_name] = LazyGet(attribute.name)
self._resources.RegisterApiByName(self._collection_info.api_name,
self._collection_info.api_version)
try:
return self._resources.Parse(
deps_lib.Get(
self.anchor.name, fallthroughs_map, parsed_args=parsed_args),
collection=self.collection,
params=params,
api_version=self._collection_info.api_version)
except deps_lib.AttributeNotFoundError as e:
raise InitializationError(
'The [{}] resource is not properly specified.\n'
'{}'.format(self.name, str(e)))
except resources.UserError as e:
raise InitializationError(str(e))
def Parse(self, attribute_to_args_map, base_fallthroughs_map,
parsed_args=None, plural=False, allow_empty=False):
"""Lazy parsing function for resource.
Generates resource based off of the parsed_args (user provided
arguments) and specified fallthrough behavior.
Args:
attribute_to_args_map: {str: str}, A map of attribute names to the names
of their associated flags.
base_fallthroughs_map: {str: [deps.Fallthrough]}, A map of attribute
names to non-argument fallthroughs, including command-level
fallthroughs.
parsed_args: the parsed Namespace.
plural: bool, True if multiple resources can be parsed, False otherwise.
allow_empty: bool, True if resource parsing is allowed to return no
resource, otherwise False.
Returns:
the initialized resources.Resource or a list of resources.Resource if the
resource argument is plural.
"""
if plural:
return self._ParseFromPluralValue(
attribute_to_args_map, base_fallthroughs_map, parsed_args,
allow_empty=allow_empty)
else:
return self._ParseFromValue(
attribute_to_args_map, base_fallthroughs_map, parsed_args,
allow_empty=allow_empty)
def BuildFullFallthroughsMap(
self, attribute_to_args_map, base_fallthroughs_map, parsed_args=None):
"""Generate fallthrough map that is used to resolve resource params.
Used as source of truth for how each attribute is resolved. It is also used
to generate help text for both plural and singular resources.
Fallthroughs are a list of objects that, when called, try different ways of
resolving a resource attribute (see googlecloudsdk.calliope.concepts.
deps_lib._Fallthrough). This method builds a map from the name of each
attribute to its list of fallthroughs.
For each attribute, adds default flag fallthroughs and fully specified
anchor fallthroughs.
Args:
attribute_to_args_map: {str: str}, A map of attribute names to the names
of their associated flags.
base_fallthroughs_map: {str: [deps.Fallthrough]}, A map of attribute
names to non-argument fallthroughs, including command-level
fallthroughs.
parsed_args: Namespace | None, user's CLI input
Returns:
{str: [deps.Fallthrough]}, a map from attribute name to all its
fallthroughs.
"""
fallthroughs_map = {**base_fallthroughs_map}
deps_map_util.AddFlagFallthroughs(
fallthroughs_map, self.attributes, attribute_to_args_map)
deps_map_util.UpdateWithValueFallthrough(
fallthroughs_map, self.anchor.name, parsed_args)
deps_map_util.AddAnchorFallthroughs(
fallthroughs_map, self.attributes, self.anchor, self.collection_info,
fallthroughs_map.get(self.anchor.name, []))
return fallthroughs_map
def _BuildFullFallthroughsMapList(
self, attribute_to_args_map, base_fallthroughs_map, parsed_args=None):
"""Builds fallthrough map for each anchor value specified in a list.
For each anchor value, create a falthrough map to derive the rest
of the resource params. For each attribute, adds flag fallthroughs
and fully specified anchor fallthroughs. For each attribute,
adds default flag fallthroughs and fully specified anchor fallthroughs.
Args:
attribute_to_args_map: {str: str}, A map of attribute names to the names
of their associated flags.
base_fallthroughs_map: FallthroughsMap, A map of attribute names to
non-argument fallthroughs, including command-level fallthroughs.
parsed_args: Namespace, used to parse the anchor value and derive
fully specified fallthroughs.
Returns:
list[FallthroughsMap], fallthrough map for each anchor value
"""
fallthroughs_map = {**base_fallthroughs_map}
deps_map_util.AddFlagFallthroughs(
fallthroughs_map, self.attributes, attribute_to_args_map)
deps_map_util.PluralizeFallthroughs(fallthroughs_map, self.anchor.name)
map_list = deps_map_util.CreateValueFallthroughMapList(
fallthroughs_map, self.anchor.name, parsed_args)
for full_map in map_list:
deps_map_util.AddAnchorFallthroughs(
full_map, self.attributes, self.anchor, self.collection_info,
full_map.get(self.anchor.name, []))
return map_list
def _ParseFromValue(
self, attribute_to_args_map, base_fallthroughs_map,
parsed_args, allow_empty=False):
"""Helper for parsing a singular resource from user input."""
fallthroughs_map = self.BuildFullFallthroughsMap(
attribute_to_args_map, base_fallthroughs_map, parsed_args)
try:
return self.Initialize(
fallthroughs_map, parsed_args=parsed_args)
except InitializationError:
if allow_empty:
return None
raise
def _ParseFromPluralValue(
self, attribute_to_args_map, base_fallthroughs_map,
parsed_args, allow_empty=False):
"""Helper for parsing a list of resources from user input."""
map_list = self._BuildFullFallthroughsMapList(
attribute_to_args_map, base_fallthroughs_map,
parsed_args=parsed_args)
parsed_resources = []
for fallthroughs_map in map_list:
resource = self.Initialize(fallthroughs_map, parsed_args=parsed_args)
parsed_resources.append(resource)
if parsed_resources:
return parsed_resources
elif allow_empty:
return []
else:
return self.Initialize(base_fallthroughs_map, parsed_args=parsed_args)
def _AttributeName(self, param_name, attribute_config, anchor=False,
is_positional=None):
"""Chooses attribute name for a param name.
If attribute_config gives an attribute name, that is used. Otherwise, if the
param is an anchor attribute, 'name' is used, or if not, param_name is used.
Args:
param_name: str, the parameter name from the collection.
attribute_config: ResourceParameterAttributeConfig, the config for the
param_name.
anchor: bool, whether the parameter is the "anchor" or the last in the
collection path.
is_positional: bool, optional value that determines if anchor argument is
a positional and reformats anchor attribute name accordingly.
Returns:
(str) the attribute name.
"""
attribute_name = attribute_config.attribute_name
if attribute_name:
# TODO(b/246766107) We need to investigate if we can reformat the
# attribute names automatically all the time. Currently, the attribute is
# only auto-formatted when positional is specified in resource spec.
# Currently, only resource specs generated by yaml files are passing in
# the is_positional value in order to avoid breaking changes.
if is_positional is None:
return attribute_name
return (format_util.SnakeCase(attribute_name) if is_positional and anchor
else format_util.KebabCase(attribute_name))
if anchor:
return 'name'
return param_name.replace('Id', '_id').lower()
def __eq__(self, other):
return (super(ResourceSpec, self).__eq__(other)
and self.disable_auto_completers == other.disable_auto_completers
and self.attribute_to_params_map == other.attribute_to_params_map)
def __hash__(self):
return super(ResourceSpec, self).__hash__() + sum(
map(hash, [self.disable_auto_completers, self.attribute_to_params_map]))
class ResourceParameterAttributeConfig(object):
"""Configuration used to create attributes from resource parameters."""
@classmethod
def FromData(cls, data):
"""Constructs an attribute config from data defined in the yaml file.
Args:
data: {}, the dict of data from the YAML file for this single attribute.
Returns:
ResourceParameterAttributeConfig
"""
attribute_name = data['attribute_name']
parameter_name = data['parameter_name']
help_text = data['help']
completer = util.Hook.FromData(data, 'completer')
completion_id_field = data.get('completion_id_field', None)
completion_request_params_list = data.get('completion_request_params', [])
completion_request_params = {
param.get('fieldName'): param.get('value')
for param in completion_request_params_list
}
if default_config := DEFAULT_RESOURCE_ATTRIBUTE_CONFIGS.get(attribute_name):
fallthroughs = default_config.fallthroughs.copy()
else:
fallthroughs = []
# Add property fallthroughs.
prop = properties.FromString(data.get('property', ''))
prop_fallthrough = prop and deps_lib.PropertyFallthrough(prop)
if prop_fallthrough and prop_fallthrough not in fallthroughs:
fallthroughs.append(prop_fallthrough)
# Add fallthroughs from python hooks.
fallthrough_data = data.get('fallthroughs', [])
fallthroughs_from_hook = []
for f in fallthrough_data:
if 'value' in f:
fallthroughs_from_hook.append(
deps_lib.ValueFallthrough(
f['value'], f['hint'] if 'hint' in f else None
)
)
elif 'hook' in f:
fallthroughs_from_hook.append(
deps_lib.Fallthrough(util.Hook.FromPath(f['hook']), hint=f['hint'])
)
fallthroughs += fallthroughs_from_hook
return cls(
name=attribute_name,
help_text=help_text,
fallthroughs=fallthroughs,
completer=completer,
completion_id_field=completion_id_field,
completion_request_params=completion_request_params,
parameter_name=parameter_name)
def __init__(self,
name=None,
help_text=None,
fallthroughs=None,
completer=None,
completion_request_params=None,
completion_id_field=None,
value_type=None,
parameter_name=None):
"""Create a resource attribute.
Args:
name: str, the name of the attribute. This controls the naming of flags
based on the attribute.
help_text: str, generic help text for any flag based on the attribute. One
special expansion is available to convert "{resource}" to the name of
the resource.
fallthroughs: [deps_lib.Fallthrough], A list of fallthroughs to use to
resolve the attribute if it is not provided on the command line.
completer: core.cache.completion_cache.Completer, the completer
associated with the attribute.
completion_request_params: {str: value}, a dict of field names to static
values to fill in for the completion request.
completion_id_field: str, the ID field of the return value in the
response for completion commands.
value_type: the type to be accepted by the attribute arg. Defaults to str.
parameter_name: the API parameter name that this attribute maps to.
"""
self.attribute_name = name
self.help_text = help_text
self.fallthroughs = fallthroughs or []
if completer and (completion_request_params or completion_id_field):
raise ValueError('Custom completer and auto-completer should not be '
'specified at the same time')
self.completer = completer
self.completion_request_params = completion_request_params
self.completion_id_field = completion_id_field
self.value_type = value_type or str
self.parameter_name = parameter_name
def ParseAttributesFromData(attributes_data, expected_param_names):
"""Parses a list of ResourceParameterAttributeConfig from yaml data.
Args:
attributes_data: dict, the attributes data defined in
command_lib/resources.yaml file.
expected_param_names: [str], the names of the API parameters that the API
method accepts. Example, ['projectsId', 'instancesId'].
Returns:
[ResourceParameterAttributeConfig].
Raises:
InvalidResourceArgumentLists: if the attributes defined in the yaml file
don't match the expected fields in the API method.
"""
raw_attributes = [
ResourceParameterAttributeConfig.FromData(a) for a in attributes_data
]
registered_param_names = [a.parameter_name for a in raw_attributes]
final_attributes = []
# TODO(b/78851830): improve the time complexity here.
for expected_name in expected_param_names:
if raw_attributes and expected_name == raw_attributes[0].parameter_name:
# Attribute matches expected, add it and continue checking.
final_attributes.append(raw_attributes.pop(0))
elif expected_name in IGNORED_FIELDS:
# Attribute doesn't match but is being ignored. Add an auto-generated
# attribute as a substitute.
# Currently, it would only be the project config.
attribute_name = IGNORED_FIELDS[expected_name]
ignored_attribute = DEFAULT_RESOURCE_ATTRIBUTE_CONFIGS.get(attribute_name)
# Manually add the parameter name, e.g. project, projectId or projectsId.
ignored_attribute.parameter_name = expected_name
final_attributes.append(ignored_attribute)
else:
# It doesn't match (or there are no more registered params) and the
# field is not being ignored, error.
raise InvalidResourceArgumentLists(expected_param_names,
registered_param_names)
if raw_attributes:
# All expected fields were processed but there are still registered
# attribute params remaining, they must be extra.
raise InvalidResourceArgumentLists(expected_param_names,
registered_param_names)
return final_attributes
DEFAULT_PROJECT_ATTRIBUTE_CONFIG = ResourceParameterAttributeConfig(
name='project',
help_text='Project ID of the Google Cloud project for the {resource}.',
fallthroughs=[
# Typically argument fallthroughs should be configured at the command
# level, but the --project flag is currently available in every command.
deps_lib.ArgFallthrough('--project'),
deps_lib.PropertyFallthrough(properties.VALUES.core.project)
])
DEFAULT_RESOURCE_ATTRIBUTE_CONFIGS = {
'project': DEFAULT_PROJECT_ATTRIBUTE_CONFIG}
_DEFAULT_CONFIGS = {'project': DEFAULT_PROJECT_ATTRIBUTE_CONFIG}