HEX
Server: Apache/2.4.65 (Ubuntu)
System: Linux ielts-store-v2 6.8.0-1036-gcp #38~22.04.1-Ubuntu SMP Thu Aug 14 01:19:18 UTC 2025 x86_64
User: root (0)
PHP: 7.2.34-54+ubuntu20.04.1+deb.sury.org+1
Disabled: pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,
Upload Files
File: //snap/google-cloud-cli/current/lib/surface/container/fleet/config_management/describe.py
# -*- 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.
"""The command to view the Config Management Feature."""

import copy

from googlecloudsdk.api_lib.container.fleet import transforms
from googlecloudsdk.api_lib.container.fleet import util
from googlecloudsdk.api_lib.util import exceptions as gcloud_exception
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.container.fleet import resources
from googlecloudsdk.command_lib.container.fleet.config_management import utils
from googlecloudsdk.command_lib.container.fleet.features import base as features_base
from googlecloudsdk.command_lib.container.fleet.membershipfeatures import base as membershipfeatures_base
from googlecloudsdk.core import exceptions
from googlecloudsdk.core import log


@base.ReleaseTracks(base.ReleaseTrack.BETA, base.ReleaseTrack.GA)
class Describe(
    # MembershipFeature must be inherited before Feature so that this class can
    # use the prior's MembershipFeatureResourceName() method.
    membershipfeatures_base.MembershipFeatureCommand,
    features_base.DescribeCommand
):
  """Describe the Config Management feature."""
  feature_name = utils.CONFIG_MANAGEMENT_FEATURE_NAME
  mf_name = utils.CONFIG_MANAGEMENT_FEATURE_NAME


@base.ReleaseTracks(base.ReleaseTrack.ALPHA)
class DescribeAlpha(Describe):
  """Describe the Config Management feature."""

  detailed_help = {
      'EXAMPLES': """
To describe the entire Config Management feature, run:

  $ {command}

To describe select membership configurations, run:

  $ {command} --memberships=example-membership-1,example-membership-2

To list the membership configurations, run:

  $ {command} --view=list

MEMBERSHIP           | LOCATION    | STATUS | INSTALL_STATE             | STOP_STATE  | SYNC_STATE    | VERSION | SYNCED_TO_FLEET_DEFAULT
-------------------- | ----------- | ------ | ------------------------- | ----------- | ------------- | ------- | ----------------------------
example-membership-1 | asia-east1  | OK     | CONFIG_SYNC_NOT_INSTALLED |             | NOT_INSTALLED |         | FLEET_DEFAULT_NOT_CONFIGURED
example-membership-2 | us-central1 | OK     | CONFIG_SYNC_INSTALLED     | NOT_STOPPED | SYNCED        | 1.22.0  | FLEET_DEFAULT_NOT_CONFIGURED
example-membership-3 | us-central1 | ERROR  | CONFIG_SYNC_INSTALLED     | NOT_STOPPED | ERROR         | 1.21.3  | FLEET_DEFAULT_NOT_CONFIGURED
      """,
  }

  @classmethod
  def Args(cls, parser):
    cls.parser = parser
    view_group = parser.add_group()
    view_group.add_argument(
        '--view',
        help='View of the feature.',
        choices={
            'full': 'Default view. Prints the entire feature.',
            'list': """
List of membership configurations. Default format is a table summary.

The `SYNCED_TO_FLEET_DEFAULT` column may display `UNKNOWN` for any membership
whose configuration has not been updated since the
[fleet-default membership configuration](https://cloud.google.com/kubernetes-engine/fleet-management/docs/manage-features)
enablement.

To view the underlying configurations instead of the table summary for select
memberships, run:

  $ {command} --view=list --format=yaml --memberships=example-membership-1,example-membership-2
            """,
        },
        default='full',
        required=True,
    )
    # Sole purpose of list_group and memberships_group is to add help text.
    list_group = view_group.add_group(
        category=base.LIST_COMMAND_FLAGS,
        help=(
            'List command flags.'
            ' Only specify when `--view=list`.'
            ' Does not include support for `--limit`.'
        ),
    )
    filter_with_examples = copy.deepcopy(base.FILTER_FLAG)
    filter_with_examples.kwargs['help'] = (
        filter_with_examples.kwargs['help'].rstrip() + """

To filter for memberships with an overall status of `ERROR`, use the
``COLUMN~VALUE'' pattern and run:

  $ {command} --view=list --filter=STATUS~ERROR

To filter for memberships that are synced to the
fleet-default membership configuration, run:

  $ {command} --view=list --filter="spec.origin.type.synced_to_fleet_default()~YES"

`SYNCED_TO_FLEET_DEFAULT` is the only column that requires filtering on the
underlying configuration field instead of the column name. An alternative is to
`--sort-by=SYNCED_TO_FLEET_DEFAULT` and filter by eye.

To filter on a configuration field not in the table summary, in this case the
Config Sync repo, run:

  $ {command} --view=list --format=yaml --filter="spec.configmanagement.configSync.git.syncRepo~https://github.com/GoogleCloudPlatform/anthos-config-management-samples.git"
        """
    )
    filter_with_examples.AddToParser(list_group)
    sort_by_with_examples = copy.deepcopy(base.SORT_BY_FLAG)
    sort_by_with_examples.kwargs['help'] = (
        sort_by_with_examples.kwargs['help'].rstrip() + """

The default table summary sorts by `LOCATION` then `MEMBERSHIP`.

To sort the table by `VERSION` instead, run:

  $ {command} --view=list --sort-by=VERSION

To sort by a configuration field not in the table summary, in this case the
Config Sync repo, and print its values in a table, run:

  $ {command} --view=list --sort-by="spec.configmanagement.configSync.git.syncRepo" --format="table(MEMBERSHIP,LOCATION,spec.configmanagement.configSync.git.syncRepo:label=REPO)"
        """
    )
    sort_by_with_examples.AddToParser(list_group)
    memberships_group = parser.add_group(
        help=(
            'Memberships to print configurations for.'
            ' Errors if a specified membership does not have a configuration'
            ' for this feature.'
        )
    )
    resources.AddMembershipResourceArg(memberships_group, plural=True)

  @staticmethod
  def enforce_flag_combinations(args):
    if (args.filter or args.sort_by) and args.view != 'list':
      raise exceptions.Error(
          '--filter and --sort-by can only be specified when --view=list.'
      )

  @gcloud_exception.CatchHTTPErrorRaiseHTTPException('{message}')
  def Run(self, args):
    self.args = args
    self.enforce_flag_combinations(args)
    feature = self.GetFeature()
    if args.memberships:
      # Verify the specified Memberships exist to distinguish them from
      # Memberships that have no specs in error messages.
      memberships = features_base.ParseMembershipsPlural(args, search=True)
      self.filter_feature_for_memberships(feature, memberships)
    if args.view == 'list':
      # Limit transforms to --view=list for simplicity; relax if necessary.
      self.parser.display_info.AddTransforms(transforms.get_transforms(
          self.hubclient, feature.fleetDefaultMemberConfig is not None,
      ))
      self.parser.display_info.AddFormat("""table(
          name.segment(-3):label=MEMBERSHIP:sort=2,
          name.segment(-5):label=LOCATION:sort=1,
          state.state.code:label=STATUS,
          state.configmanagement.configSyncState.state:label=INSTALL_STATE,
          state.configmanagement.configSyncState.clusterLevelStopSyncingState:label=STOP_STATE,
          state.configmanagement.configSyncState.syncState.code:label=SYNC_STATE,
          state.configmanagement.membershipSpec.version:label=VERSION,
          spec.origin.type.synced_to_fleet_default():label=SYNCED_TO_FLEET_DEFAULT
      )""")
      return self.construct_membership_features_list(feature)
    return feature

  def construct_membership_features_list(self, feature):
    if feature.unreachable:
      log.warning('Membership configuration list may be incomplete.'
                  ' Unreachable locations: %s', feature.unreachable)
    if not feature.membershipSpecs:
      return []
    states = {}
    if feature.membershipStates:
      states = {
          util.MembershipPartialName(entry.key): entry.value
          for entry in feature.membershipStates.additionalProperties
      }
    project_id = self.Project(number=False)
    project_number = self.Project(number=True)
    return [
        # Mimic v2 MembershipFeature. Leaves room for alternate
        # ListMembershipFeatures API call implementation. Uses dictionary
        # instead of generated API message class to avoid potential v1 vs. v2
        # configuration discrepancy errors. (Since we don't promise the output
        # of --view=list passes into other commands as input, any v1 vs. v2
        # discrepancy is forgivable.)
        {
            # Use project name instead of number for readability, to follow
            # resource name convention, and to match the v2 API output for
            # MembershipFeature. Do not use functions like
            # util.MembershipFeatureResourceName() that parse the resources
            # REGISTRY due to poor performance, which doesn't scale with the
            # number of Memberships.
            'name': self.MembershipFeatureResourceName(
                entry.key.replace(
                    f'projects/{project_number}/',
                    f'projects/{project_id}/',
                    1
                )
            ),
            'spec': entry.value,
            **(
                {'state': states[util.MembershipPartialName(entry.key)]}
                if util.MembershipPartialName(entry.key) in states
                else {}
            )
        }
        for entry in feature.membershipSpecs.additionalProperties
    ]

  def Epilog(self, resources_were_displayed):
    # Reference Epilog method on ListCommand.
    if not resources_were_displayed and self.args.view == 'list':
      log.status.Print('Listed 0 items.')