# -*- coding: utf-8 -*- # # Copyright 2025 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. """Functions for resource arguments in fleet commands.""" from __future__ import absolute_import from __future__ import division from __future__ import unicode_literals import re from googlecloudsdk.api_lib.container.fleet import util from googlecloudsdk.calliope import arg_parsers from googlecloudsdk.calliope import base as calliope_base from googlecloudsdk.calliope import exceptions as calliope_exceptions from googlecloudsdk.calliope.concepts import concepts from googlecloudsdk.calliope.concepts import deps from googlecloudsdk.command_lib.container.fleet import api_util from googlecloudsdk.command_lib.container.fleet import util as cmd_util from googlecloudsdk.command_lib.util.concepts import concept_parsers from googlecloudsdk.core import exceptions from googlecloudsdk.core import properties from googlecloudsdk.core.console import console_io _LOCATION_RE = re.compile('/locations/([a-z0-9-]+)/') def PromptForMembership(memberships=None, flag='--membership', message='Please specify a membership:\n', cancel=True): """Prompt the user for a membership from a list of memberships in the fleet. This method is referenced by fleet and feature commands as a fallthrough for getting the memberships when required. Args: memberships: List of memberships to prompt from flag: The name of the membership flag, used in the error message message: The message given to the user describing the prompt. cancel: Whether to include a "cancel" option. Returns: The membership specified by the user (str), or None if unable to prompt. Raises: OperationCancelledError if the prompt is cancelled by user RequiredArgumentException if the console is unable to prompt """ if not console_io.CanPrompt(): raise calliope_exceptions.RequiredArgumentException( flag, ('Cannot prompt a console for membership. Membership is ' 'required. Please specify `{}` to select at ' 'least one membership.'.format(flag))) if memberships is None: memberships, unreachable = api_util.ListMembershipsFull() if unreachable: raise exceptions.Error( ('Locations {} are currently unreachable. Please specify ' 'memberships using `--location` or the full resource name ' '(projects/*/locations/*/memberships/*)').format(unreachable)) if not memberships: raise exceptions.Error('No Memberships available in the fleet.') idx = console_io.PromptChoice( MembershipPartialNames(memberships), message=message, cancel_option=cancel) return memberships[idx] if idx is not None else None # For CLI output (e.g. prompts), the LOCATION/ID format # (e.g. us-central1/my-membership) is more readable than # the full resource name def MembershipPartialNames(memberships): """Converts a list of full membership names to LOCATION/ID format.""" return [util.MembershipPartialName(m) for m in memberships] def _LocationAttributeConfig(help_text=''): """Create location attributes in resource argument. Args: help_text: If set, overrides default help text for `--location` Returns: Location resource argument parameter config """ fallthroughs = [ deps.ArgFallthrough('--location'), deps.PropertyFallthrough(properties.VALUES.gkehub.location), ] return concepts.ResourceParameterAttributeConfig( name='location', help_text=help_text if help_text else ('Location for the {resource}.'), fallthroughs=fallthroughs) def _BasicAttributeConfig(attr_name, help_text=''): """Create basic attributes in resource argument. Args: attr_name: Name of the resource help_text: If set, overrides default help text Returns: Resource argument parameter config """ return concepts.ResourceParameterAttributeConfig( name=attr_name, help_text=help_text if help_text else ('Name of the {resource}.')) # Note that the membership_help argument does not work. def AddMembershipResourceArg(parser, api_version='v1', positional=False, plural=False, membership_required=False, flag_override='', membership_help='', location_help=''): """Add resource arg for projects/{}/locations/{}/memberships/{}.""" flag_name = '--membership' if flag_override: flag_name = flag_override elif positional: # Flags without '--' prefix are automatically positional flag_name = 'MEMBERSHIP_NAME' elif plural: flag_name = '--memberships' spec = concepts.ResourceSpec( 'gkehub.projects.locations.memberships', api_version=api_version, resource_name='membership', plural_name='memberships', disable_auto_completers=True, projectsId=concepts.DEFAULT_PROJECT_ATTRIBUTE_CONFIG, locationsId=_LocationAttributeConfig(location_help), membershipsId=_BasicAttributeConfig( 'memberships' if plural else 'membership', membership_help)) concept_parsers.ConceptParser.ForResource( flag_name, spec, 'The group of arguments defining one or more memberships.' if plural else 'The group of arguments defining a membership.', plural=plural, required=membership_required).AddToParser(parser) def MembershipLocationSpecified(args, flag_override=''): """Returns whether a membership location is specified in args.""" if args.IsSpecified('location'): return True if args.IsKnownAndSpecified('membership') and _LOCATION_RE.search( args.membership) is not None: return True if args.IsKnownAndSpecified('MEMBERSHIP_NAME') and _LOCATION_RE.search( args.MEMBERSHIP_NAME) is not None: return True if args.IsKnownAndSpecified('memberships') and all( [_LOCATION_RE.search(m) is not None for m in args.memberships]): return True if args.IsKnownAndSpecified(flag_override) and _LOCATION_RE.search( args.GetValue(flag_override)) is not None: return True return False def SearchMembershipResource(args, flag_override='', filter_cluster_missing=False): """Searches the fleet for an ambiguous membership provided in args. Only necessary if location is ambiguous, i.e. MembershipLocationSpecified(args) is False, or this behavior is necessary for backwards compatibility. If flag_override is unset, the argument must be called `MEMBERSHIP_NAME` if positional and `--membership` otherwise. Runs a ListMemberships API call to verify the membership exists. Args: args: arguments provided to a command, including a membership resource arg flag_override: a custom membership flag filter_cluster_missing: whether to filter out memberships that are missing a cluster. Returns: A membership resource name string (e.g. projects/x/locations/y/memberships/z) Raises: googlecloudsdk.core.exceptions.Error: unable to find the membership in the fleet """ if MembershipLocationSpecified(args) and api_util.GetMembership( MembershipResourceName(args)): return MembershipResourceName(args) if args.IsKnownAndSpecified(flag_override): arg_membership = getattr(args, flag_override) elif args.IsKnownAndSpecified('MEMBERSHIP_NAME'): arg_membership = args.MEMBERSHIP_NAME elif args.IsKnownAndSpecified('membership'): arg_membership = args.membership else: return None all_memberships, unavailable = api_util.ListMembershipsFull( filter_cluster_missing=filter_cluster_missing) if unavailable: raise exceptions.Error( ('Locations {} are currently unreachable. Please specify ' 'memberships using `--location` or the full resource name ' '(projects/*/locations/*/memberships/*)').format(unavailable)) if not all_memberships: raise exceptions.Error('No memberships available in the fleet.') # Search all memberships for specified membership found = [] for existing_membership in all_memberships: if arg_membership == util.MembershipShortname(existing_membership): found.append(existing_membership) if not found: raise exceptions.Error( 'Membership {} not found in the fleet.'.format(arg_membership)) elif len(found) > 1: raise AmbiguousMembershipError(arg_membership) return found[0] def SearchMembershipResourcesPlural(args, filter_cluster_missing=False): """Searches the fleet for the membership resources provided in args. Only necessary if location is ambiguous, i.e. MembershipLocationSpecified(args) is False. Assumes the argument is called `--membership`, `--memberships` if plural, or `MEMBERSHIP_NAME` if positional. Runs ListMemberships API call to verify the membership exists. Args: args: arguments provided to a command, including a membership resource arg filter_cluster_missing: whether to filter out memberships that are missing a cluster. Returns: A list of membership resource names (e.g. ["projects/x/locations/y/memberships/z"]) Raises: googlecloudsdk.core.exceptions.Error: unable to find a membership in the fleet """ if args.IsKnownAndSpecified('memberships'): arg_memberships = args.memberships else: return None all_memberships, unavailable = api_util.ListMembershipsFull( filter_cluster_missing=filter_cluster_missing) if unavailable: raise exceptions.Error( ('Locations [{}] are currently unreachable. Please specify ' 'memberships using `--location` or the full resource name ' '(projects/*/locations/*/memberships/*)').format(unavailable)) if not all_memberships: raise exceptions.Error('No memberships available in the fleet.') memberships = [] for arg_membership in arg_memberships: # Search all memberships for specified membership found = [] for existing_membership in all_memberships: if arg_membership == util.MembershipShortname(existing_membership): found.append(existing_membership) if not found: raise exceptions.Error( 'Membership {} not found in the fleet.'.format(arg_membership)) elif len(found) > 1: raise AmbiguousMembershipError(arg_membership) memberships.append(found[0]) return memberships def AmbiguousMembershipError(membership): return exceptions.Error( ('Multiple memberships named {} found in the fleet. Please use ' '`--location` or full resource name ' '(projects/*/locations/*/memberships/*) to specify.').format(membership)) def MembershipResourceName(args, flag_override=''): """Gets a membership resource name from a membership resource argument. If flag_override is unset, the argument must be `MEMBERSHIP_NAME` if positional and `--membership` otherwise. Args: args: arguments provided to a command, including a membership resource arg flag_override: a custom membership flag name Returns: The membership resource name (e.g. projects/x/locations/y/memberships/z) """ if args.IsKnownAndSpecified(flag_override): return args.CONCEPTS.GetValue(flag_override).Parse().RelativeName() if args.IsKnownAndSpecified('MEMBERSHIP_NAME'): return args.CONCEPTS.membership_name.Parse().RelativeName() return args.CONCEPTS.membership.Parse().RelativeName() def PluralMembershipsResourceNames(args): """Gets a list of membership resource names from a --memberships resource arg. Args: args: arguments provided to a command, including a plural memberships resource arg Returns: A list of membership resource names (e.g. projects/x/locations/y/memberships/z) """ return [m.RelativeName() for m in args.CONCEPTS.memberships.Parse()] def UseRegionalMemberships(track=None): """Returns whether regional memberships should be included. This will be updated as regionalization is released, and eventually deleted when it is fully rolled out. Args: track: The release track of the command Returns: A bool, whether regional memberships are supported for the release track in the active environment """ return (track is calliope_base.ReleaseTrack.ALPHA) and ( cmd_util.APIEndpoint() == cmd_util.AUTOPUSH_API) def InProdRegionalAllowlist(project, track=None): """Returns whether project is allowlisted for regional memberships in Prod. This will be updated as regionalization is released, and eventually deleted when it is fully rolled out. Args: project: The parent project ID of the membership track: The release track of the command Returns: A bool, whether project is allowlisted for regional memberships in Prod """ prod_regional_allowlist = [ 'gkeconnect-prober', 'gkeconnect-e2e', 'gkehub-cep-test', 'connectgateway-gke-testing', 'xuebinz-gke', 'kolber-anthos-testing', 'anthonytong-hub2', 'wenjuntoy2', 'hub-regionalisation-test', # For Cloud Console UI testing. 'hub-regionalisation-test-2', # For Cloud Console UI testing. 'a4vm-ui-tests-3', # For Cloud Console UI testing. 'm4a-ui-playground-1', # For Cloud Console UI testing. 'anthos-cl-e2e-tests', 'a4vm-ui-playground', 'm4a-ui-playground-1', ] return track is calliope_base.ReleaseTrack.ALPHA and ( project in prod_regional_allowlist) def GetMembershipProjects(memberships): """Returns all unique project identifiers of the given membership names. ListMemberships should use the same identifier (all number or all ID) in membership names. Users can convert their own project identifiers for manually entering arguments. Args: memberships: A list of full membership resource names Returns: A list of project identifiers in the parents of the memberships Raises: googlecloudsdk.core.exceptions.Error if unable to parse any membership name """ projects = set() for m in memberships: match = re.match(r'projects\/(.*)\/locations\/(.*)\/memberships\/(.*)', m) if not match: raise exceptions.Error('Unable to parse membership {} (expected ' 'projects/*/locations/*/memberships/*)'.format(m)) projects.add(match.group(1)) return list(projects) def ParseMembershipArg(args, membership_flag='MEMBERSHIP_NAME'): """Returns a membership on which to run the command, given the arguments. This function is currently only used by the unregister command. This logic should be combined with the feature ParseMembership function in a later CL. Allows for `MEMBERSHIP_NAME` positional flag. Args: args: object containing arguments passed as flags with the command membership_flag: the membership flag used to pass in the memberhip resource Returns: membership: A membership resource name string Raises: exceptions.Error: no memberships were found or memberships are invalid calliope_exceptions.RequiredArgumentException: membership was not provided """ # If a membership is provided (positional arg) if args.IsKnownAndSpecified(membership_flag): if MembershipLocationSpecified(args): return MembershipResourceName(args) else: return SearchMembershipResource(args) raise calliope_exceptions.RequiredArgumentException( membership_flag, 'membership is required for this command.') def _DefaultToGlobalLocationAttributeConfig(help_text=''): """Create basic attributes that fallthrough location to global in resource argument. Args: help_text: If set, overrides default help text Returns: Resource argument parameter config """ return concepts.ResourceParameterAttributeConfig( name='location', fallthroughs=[ deps.Fallthrough( function=cmd_util.DefaultToGlobal, hint='global is the only supported location', ) ], help_text=help_text if help_text else ('Name of the {resource}.'), ) def AddScopeResourceArg( parser, flag_name='NAME', api_version='v1', scope_help='', required=False, group=None, ): """Add resource arg for projects/{}/locations/{}/scopes/{}.""" spec = concepts.ResourceSpec( 'gkehub.projects.locations.scopes', api_version=api_version, resource_name='scope', plural_name='scopes', disable_auto_completers=True, projectsId=concepts.DEFAULT_PROJECT_ATTRIBUTE_CONFIG, locationsId=_DefaultToGlobalLocationAttributeConfig(), scopesId=_BasicAttributeConfig('scope', scope_help), ) concept_parsers.ConceptParser.ForResource( flag_name, spec, 'The group of arguments defining the Fleet Scope.', plural=False, required=required, group=group, # This hides the location flag as we only allow global scope. flag_name_overrides={'location': ''}, ).AddToParser(parser) def AddScopeNamespaceResourceArg( parser, flag_name='NAMESPACE', api_version='v1', namespace_help='', required=False, ): """Add resource arg for projects/{}/locations/{}/scopes/{}/namespaces/{}.""" spec = concepts.ResourceSpec( 'gkehub.projects.locations.scopes.namespaces', api_version=api_version, resource_name='namespace', plural_name='namespaces', disable_auto_completers=True, projectsId=concepts.DEFAULT_PROJECT_ATTRIBUTE_CONFIG, locationsId=_DefaultToGlobalLocationAttributeConfig(), scopesId=_BasicAttributeConfig('scope', 'the'), namespacesId=_BasicAttributeConfig('namespace', namespace_help), ) concept_parsers.ConceptParser.ForResource( flag_name, spec, 'The group of arguments defining the Fleet Namespace.', plural=False, required=required, # This hides the location flag as we only allow global scope. flag_name_overrides={'location': ''}, ).AddToParser(parser) def AddScopeRBACResourceArg(parser, api_version='v1', rbacrb_help=''): """Add resource arg for projects/{}/locations/{}/scopes/{}/rbacrolebindings/{}.""" # Flags without '--' prefix are automatically positional flag_name = 'NAME' spec = concepts.ResourceSpec( 'gkehub.projects.locations.scopes.rbacrolebindings', api_version=api_version, resource_name='rbacrolebinding', plural_name='rbacrolebindings', disable_auto_completers=True, projectsId=concepts.DEFAULT_PROJECT_ATTRIBUTE_CONFIG, locationsId=_LocationAttributeConfig(), scopesId=_BasicAttributeConfig('scope', ''), rbacrolebindingsId=_BasicAttributeConfig('rbacrolebinding', rbacrb_help), ) concept_parsers.ConceptParser.ForResource( flag_name, spec, 'The group of arguments defining an RBACRoleBinding.', plural=False, required=True, ).AddToParser(parser) def AddRBACResourceArg(parser, api_version='v1', rbacrb_help=''): """Add resource arg for projects/{}/locations/{}/memberships/{}.""" # Flags without '--' prefix are automatically positional flag_name = 'NAME' spec = concepts.ResourceSpec( 'gkehub.projects.locations.namespaces.rbacrolebindings', api_version=api_version, resource_name='rbacrolebinding', plural_name='rbacrolebindings', disable_auto_completers=True, projectsId=concepts.DEFAULT_PROJECT_ATTRIBUTE_CONFIG, locationsId=_LocationAttributeConfig(), namespacesId=_BasicAttributeConfig('namespace', ''), rbacrolebindingsId=_BasicAttributeConfig('rbacrolebinding', rbacrb_help)) concept_parsers.ConceptParser.ForResource( flag_name, spec, 'The group of arguments defining an RBACRoleBinding.', plural=False, required=True).AddToParser(parser) def AddUpdateNamespaceLabelsFlags(parser): """Adds flags to an argparse parser for updating namespace labels. Args: parser: The argparse parser to add the flags to. """ _GetUpdateNamespaceLabelsFlag('namespace').AddToParser(parser) remove_group = parser.add_mutually_exclusive_group() _GetClearNamespaceLabelsFlag('namespace').AddToParser( remove_group ) _GetRemoveNamespaceLabelsFlag('namespace').AddToParser(remove_group) def UpdateScopeLabelsFlags(): remove_group = calliope_base.ArgumentGroup(mutex=True) remove_group.AddArgument( _GetClearNamespaceLabelsFlag('scope') ) remove_group.AddArgument( _GetRemoveNamespaceLabelsFlag('scope') ) return [ _GetUpdateNamespaceLabelsFlag('scope'), remove_group, ] def AddCreateNamespaceLabelsFlags(parser): """Adds flags to an argparse parser for creating namespace labels. Args: parser: The argparse parser to add the flags to. """ _GetCreateNamespaceLabelsFlag('namespace').AddToParser(parser) def CreateScopeLabelsFlags(): return [_GetCreateNamespaceLabelsFlag('scope')] def _GetClearNamespaceLabelsFlag(resource_type): labels_name = 'namespace-labels' return calliope_base.Argument( '--clear-{}'.format(labels_name), action='store_true', help="""\ Remove all {resource_type}-level labels from the cluster namespace. If `--update-{labels}` is also specified then `--clear-{labels}` is applied first. For example, to remove all labels: $ {{command}} {resource_type}_name --clear-{labels} To remove all existing {resource_type}-level labels and create two new labels, ``foo'' and ``baz'': $ {{command}} {resource_type}_name --clear-{labels} --update-{labels} foo=bar,baz=qux """.format(labels=labels_name, resource_type=resource_type)) def _GetRemoveNamespaceLabelsFlag(resource_type): labels_name = 'namespace-labels' return calliope_base.Argument( '--remove-{}'.format(labels_name), metavar='KEY', type=arg_parsers.ArgList(), action=arg_parsers.UpdateAction, help="""\ List of {resource_type}-level label keys to remove in the cluster namespace. If a label does not exist it is silently ignored. If `--update-{labels}` is also specified then `--update-{labels}` is applied first. """.format(labels=labels_name, resource_type=resource_type)) def _GetUpdateNamespaceLabelsFlag(resource_type): """Makes a base.Argument for the `--update-namespace-labels` flag.""" labels_name = 'namespace-labels' return calliope_base.Argument( '--update-{}'.format(labels_name), metavar='KEY=VALUE', type=arg_parsers.ArgDict(), action=arg_parsers.UpdateAction, help="""\ List of {resource_type}-level label KEY=VALUE pairs to update in the cluster namespace. If a label exists, its value is modified. Otherwise, a new label is' created.""".format(resource_type=resource_type)) def _GetCreateNamespaceLabelsFlag(resource_type): labels_name = 'namespace-labels' return calliope_base.Argument( '--{}'.format(labels_name), metavar='KEY=VALUE', type=arg_parsers.ArgDict(), action=arg_parsers.UpdateAction, help="""\ List of {resource_type}-level label KEY=VALUE pairs to add. """.format(resource_type=resource_type)) def RBACResourceName(args): """Gets an RBACRoleBinding resource name from a resource argument. Assumes the argument is called NAME. Args: args: arguments provided to a command, including an rbacRB resource arg Returns: The rbacRB resource name (e.g. projects/x/locations/global/namespaces/y/rbacrolebindings/z projects/x/locations/global/scopes/y/rbacrolebindings/z) """ return args.CONCEPTS.name.Parse().RelativeName() def AddMembershipBindingResourceArg(parser, api_version='v1', binding_help=''): """Add resource arg for projects/{}/locations/{}/memberships/{}/bindings/{}.""" # Flags without '--' prefix are automatically positional flag_name = 'BINDING' spec = concepts.ResourceSpec( 'gkehub.projects.locations.memberships.bindings', api_version=api_version, resource_name='binding', plural_name='bindings', disable_auto_completers=True, projectsId=concepts.DEFAULT_PROJECT_ATTRIBUTE_CONFIG, locationsId=_LocationAttributeConfig(), membershipsId=_BasicAttributeConfig('membership', ''), bindingsId=_BasicAttributeConfig('binding', binding_help)) concept_parsers.ConceptParser.ForResource( flag_name, spec, 'The group of arguments defining a Membership Binding.', plural=False, required=True).AddToParser(parser) def MembershipBindingResourceName(args): """Gets a Membership-Binding resource name from a resource argument. Assumes the argument is called BINDING. Args: args: arguments provided to a command, including a Binding resource arg Returns: The Binding resource name (e.g. projects/x/locations/l/memberships/y/bindings/z) """ return args.CONCEPTS.binding.Parse().RelativeName() def AddRolloutResourceArg(parser, api_version='v1'): """Add resource arg for projects/{}/locations/{}/rollouts/{}.""" # Flags without '--' prefix are automatically positional spec = concepts.ResourceSpec( 'gkehub.projects.locations.rollouts', api_version=api_version, resource_name='rollout', plural_name='rollouts', disable_auto_completers=True, projectsId=concepts.DEFAULT_PROJECT_ATTRIBUTE_CONFIG, locationsId=_DefaultToGlobalLocationAttributeConfig(), rolloutsId=_BasicAttributeConfig('rollout'), ) concept_parsers.ConceptParser.ForResource( name='rollout', resource_spec=spec, group_help='The group of arguments defining a Fleet Rollout.', plural=False, required=True, # This hides the location flag as we only allow global scope. flag_name_overrides={'location': ''}, ).AddToParser(parser) def AddRolloutSequenceResourceArg(parser, api_version='v1'): """Add resource arg for projects/{}/locations/{}/rolloutSequences/{}.""" # Flags without '--' prefix are automatically positional spec = concepts.ResourceSpec( 'gkehub.projects.locations.rolloutSequences', api_version=api_version, resource_name='rolloutSequence', plural_name='rolloutSequences', disable_auto_completers=True, projectsId=concepts.DEFAULT_PROJECT_ATTRIBUTE_CONFIG, locationsId=_DefaultToGlobalLocationAttributeConfig(), rolloutSequencesId=_BasicAttributeConfig('rollout_sequence'), ) concept_parsers.ConceptParser.ForResource( name='rolloutSequence', resource_spec=spec, group_help='The group of arguments defining a Rollout Sequence.', plural=False, required=True, # This hides the location flag as we only allow global scope. flag_name_overrides={'location': ''}, ).AddToParser(parser) def RolloutSequenceResourceName(args): """Gets a RolloutSequence resource name from a resource argument. Assumes the argument is called ROLLOUTSEQUENCE. Args: args: arguments provided to a command, including a RolloutSequence resource arg Returns: The RolloutSequence resource name (e.g. projects/x/locations/l/rolloutSequences/z) """ return args.CONCEPTS.rolloutsequence.Parse().RelativeName() def AddWorkloadIdentityPoolResourceArg(parser, api_version='v1'): """Add resource arg for projects/{}/locations/{}/workloadidentitypools/{}.""" # Flags without '--' prefix are automatically positional flag_name = 'WORKLOAD_IDENTITY_POOL' spec = concepts.ResourceSpec( 'iam.projects.locations.workloadIdentityPools', api_version=api_version, resource_name='workloadidentitypool', plural_name='workloadidentitypools', projectsId=concepts.DEFAULT_PROJECT_ATTRIBUTE_CONFIG, locationsId=_LocationAttributeConfig(), workloadIdentityPoolsId=_BasicAttributeConfig('workloadidentitypool'), ) concept_parsers.ConceptParser.ForResource( name=flag_name, resource_spec=spec, group_help='The group of arguments defining a Workload Identity Pool.', plural=False, required=True, # This hides the location flag as we only allow global scope. flag_name_overrides={'location': ''}, ).AddToParser(parser) def WorkloadIdentityPoolResourceName(args): """Gets a WorkloadIdentityPool resource name from a resource argument. Assumes the argument is called WORKLOAD_IDENTITY_POOL. Args: args: arguments provided to a command, including a WorkloadIdentityPool resource arg Returns: The WorkloadIdentityPool resource name (e.g. projects/x/locations/l/workloadidentitypools/z) """ return args.CONCEPTS.workload_identity_pool.Parse().RelativeName()