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/394/lib/googlecloudsdk/command_lib/container/gkemulticloud/kubeconfig.py
# -*- coding: utf-8 -*- #
# Copyright 2022 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.
"""Utilities for generating kubeconfig entries."""

from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals

import base64
import os
import subprocess

from googlecloudsdk.api_lib.container import kubeconfig as kubeconfig_util
from googlecloudsdk.api_lib.container import util
from googlecloudsdk.command_lib.container.fleet import gateway
from googlecloudsdk.command_lib.container.fleet import gwkubeconfig_util
from googlecloudsdk.command_lib.container.gkemulticloud import errors
from googlecloudsdk.core import config
from googlecloudsdk.core import log
from googlecloudsdk.core import properties
from googlecloudsdk.core.util import platforms
from googlecloudsdk.core.util import semver

COMMAND_DESCRIPTION = """
Fetch credentials for a running {cluster_type}.

This command updates a kubeconfig file with appropriate credentials and
endpoint information to point kubectl at a specific {cluster_type}.

By default, credentials are written to ``HOME/.kube/config''.
You can provide an alternate path by setting the ``KUBECONFIG'' environment
variable. If ``KUBECONFIG'' contains multiple paths, the first one is used.

This command enables switching to a specific cluster, when working
with multiple clusters. It can also be used to access a previously created
cluster from a new workstation.

By default, the command will configure kubectl to automatically refresh its
credentials using the same identity as the gcloud command-line tool.
If you are running kubectl as part of an application, it is recommended to use
[application default credentials](https://cloud.google.com/docs/authentication/production).
To configure a kubeconfig file to use application default credentials, set
the ``container/use_application_default_credentials''
[Google Cloud CLI property](https://cloud.google.com/sdk/docs/properties) to
``true'' before running the command.

See [](https://cloud.google.com/kubernetes-engine/docs/kubectl) for
kubectl documentation.
"""

COMMAND_EXAMPLE = """
To get credentials of a cluster named ``my-cluster'' managed in location ``us-west1'',
run:

$ {command} my-cluster --location=us-west1
"""

NOT_RUNNING_MSG = """\
Cluster {} is not RUNNING. The Kubernetes API may or may not be available. \
Check the cluster status for more information."""

STILL_PROVISIONING_MSG = 'Is it still PROVISIONING?'


def GenerateContext(kind, project_id, location, cluster_id):
  """Generates a kubeconfig context for an Anthos Multi-Cloud cluster.

  Args:
    kind: str, kind of the cluster e.g. aws, azure.
    project_id: str, project ID accociated with the cluster.
    location: str, Google location of the cluster.
    cluster_id: str, ID of the cluster.

  Returns:
    The context for the kubeconfig entry.
  """
  template = 'gke_{kind}_{project_id}_{location}_{cluster_id}'
  return template.format(
      kind=kind, project_id=project_id, location=location, cluster_id=cluster_id
  )


def GenerateAuthProviderCmdArgs(kind, cluster_id, location, project):
  """Generates command arguments for kubeconfig's authorization provider.

  Args:
    kind: str, kind of the cluster e.g. aws, azure.
    cluster_id: str, ID of the cluster.
    location: str, Google location of the cluster.
    project: str, Google Cloud project of the cluster.

  Returns:
    The command arguments for kubeconfig's authorization provider.
  """
  template = (
      'container {kind} clusters print-access-token '
      '{cluster_id} --project={project} --location={location} '
      '--format=json --exec-credential'
  )
  return template.format(
      kind=kind, cluster_id=cluster_id, location=location, project=project
  )


def GenerateAttachedKubeConfig(cluster, cluster_id, context, cmd_path):
  """Generates a kubeconfig entry for an Anthos Multi-cloud attached cluster.

  Args:
    cluster: object, Anthos Multi-cloud cluster.
    cluster_id: str, the cluster ID.
    context: str, context for the kubeconfig entry.
    cmd_path: str, authentication provider command path.
  """
  kubeconfig = kubeconfig_util.Kubeconfig.Default()
  # Use the same key for context, cluster, and user.
  kubeconfig.contexts[context] = kubeconfig_util.Context(
      context, context, context
  )

  _CheckPreqs()
  _ConnectGatewayKubeconfig(kubeconfig, cluster, cluster_id, context, cmd_path)

  kubeconfig.SetCurrentContext(context)
  kubeconfig.SaveToFile()
  log.status.Print(
      'A new kubeconfig entry "{}" has been generated and set as the '
      'current context.'.format(context)
  )


def GenerateKubeconfig(
    cluster, cluster_id, context, cmd_path, cmd_args, private_ep=False
):
  """Generates a kubeconfig entry for an Anthos Multi-cloud cluster.

  Args:
    cluster: object, Anthos Multi-cloud cluster.
    cluster_id: str, the cluster ID.
    context: str, context for the kubeconfig entry.
    cmd_path: str, authentication provider command path.
    cmd_args: str, authentication provider command arguments.
    private_ep: bool, whether to use private VPC for authentication.

  Raises:
      Error: don't have the permission to open kubeconfig file.
  """
  kubeconfig = kubeconfig_util.Kubeconfig.Default()
  # Use the same key for context, cluster, and user.
  kubeconfig.contexts[context] = kubeconfig_util.Context(
      context, context, context
  )

  # Only default to use Connect Gateway for 1.21+.
  version = _GetSemver(cluster, cluster_id)
  if private_ep or version < semver.SemVer('1.21.0'):
    _CheckPreqs(private_endpoint=True)
    _PrivateVPCKubeconfig(
        kubeconfig, cluster, cluster_id, context, cmd_path, cmd_args
    )
  else:
    _CheckPreqs()
    _ConnectGatewayKubeconfig(
        kubeconfig, cluster, cluster_id, context, cmd_path
    )

  kubeconfig.SetCurrentContext(context)
  kubeconfig.SaveToFile()
  log.status.Print(
      'A new kubeconfig entry "{}" has been generated and set as the '
      'current context.'.format(context)
  )


def _CheckPreqs(private_endpoint=False):
  """Checks the prerequisites to run get-credentials commands."""
  util.CheckKubectlInstalled()
  if not private_endpoint:
    project_id = properties.VALUES.core.project.GetOrFail()
    gateway.CheckGatewayApiEnablement(
        project_id, _GetConnectGatewayEndpoint()
    )


def _ConnectGatewayKubeconfig(
    kubeconfig, cluster, cluster_id, context, cmd_path
):
  """Generates the Connect Gateway kubeconfig entry.

  Args:
    kubeconfig: object, Kubeconfig object.
    cluster: object, Anthos Multi-cloud cluster.
    cluster_id: str, the cluster ID.
    context: str, context for the kubeconfig entry.
    cmd_path: str, authentication provider command path.

  Raises:
      errors.MissingClusterField: cluster is missing required fields.
  """
  if cluster.fleet is None or cluster.fleet.membership is None:
    raise errors.MissingClusterField(
        cluster_id, 'Fleet membership', STILL_PROVISIONING_MSG
    )
  server = 'https://{}/v1/{}'.format(
      _GetConnectGatewayEndpoint(), cluster.fleet.membership
  )
  user_kwargs = {'auth_provider': 'gcp', 'auth_provider_cmd_path': cmd_path}
  kubeconfig.users[context] = kubeconfig_util.User(context, **user_kwargs)
  kubeconfig.clusters[context] = gwkubeconfig_util.Cluster(context, server)


def _PrivateVPCKubeconfig(
    kubeconfig, cluster, cluster_id, context, cmd_path, cmd_args
):
  """Generates the kubeconfig entry to connect using private VPC.

  Args:
    kubeconfig: object, Kubeconfig object.
    cluster: object, Anthos Multi-cloud cluster.
    cluster_id: str, the cluster ID.
    context: str, context for the kubeconfig entry.
    cmd_path: str, authentication provider command path.
    cmd_args: str, authentication provider command arguments.
  """
  user = {}
  user['exec'] = _ExecAuthPlugin(cmd_path, cmd_args)
  kubeconfig.users[context] = {'name': context, 'user': user}

  cluster_kwargs = {}
  if cluster.clusterCaCertificate is None:
    log.warning('Cluster is missing certificate authority data.')
  else:
    cluster_kwargs['ca_data'] = _GetCaData(cluster.clusterCaCertificate)
  if cluster.endpoint is None:
    raise errors.MissingClusterField(
        cluster_id, 'endpoint', STILL_PROVISIONING_MSG
    )
  kubeconfig.clusters[context] = kubeconfig_util.Cluster(
      context, 'https://{}'.format(cluster.endpoint), **cluster_kwargs
  )


def ValidateClusterVersion(cluster, cluster_id):
  """Validates the cluster version.

  Args:
    cluster: object, Anthos Multi-cloud cluster.
    cluster_id: str, the cluster ID.

  Raises:
      UnsupportedClusterVersion: cluster version is not supported.
      MissingClusterField: expected cluster field is missing.
  """
  version = _GetSemver(cluster, cluster_id)
  if version < semver.SemVer('1.20.0'):
    raise errors.UnsupportedClusterVersion(
        'The command get-credentials is supported in cluster version 1.20 '
        'and newer. For older versions, use get-kubeconfig.'
    )


def _GetCaData(pem):
  # Field certificate-authority-data in kubeconfig
  # expects a base64 encoded string of a PEM.
  return base64.b64encode(pem.encode('utf-8')).decode('utf-8')


def _GetSemver(cluster, cluster_id):
  if cluster.controlPlane is None or cluster.controlPlane.version is None:
    raise errors.MissingClusterField(cluster_id, 'version')
  version = cluster.controlPlane.version
  # The dev version e.g. 1.21-next does not conform to semantic versioning.
  # Replace the -next suffix before parsing semver for version comparison.
  if version.endswith('-next'):
    v = version.replace('-next', '.0', 1)
    return semver.SemVer(v)
  return semver.SemVer(version)


def _GetConnectGatewayEndpoint():
  """Gets the corresponding Connect Gateway endpoint for Multicloud environment.

  http://g3doc/cloud/kubernetes/multicloud/g3doc/oneplatform/team/how-to/hub

  Returns:
    The Connect Gateway endpoint.

  Raises:
    Error: Unknown API override.
  """
  # TODO(b/196964566): Use per-region URL for Connect Gatway once GA e.g.
  # us-west1-connectgateway.googleapis.com.
  endpoint = properties.VALUES.api_endpoint_overrides.gkemulticloud.Get()
  # Multicloud overrides prod endpoint at run time with the regionalized version
  # so we can't simply check that endpoint is not overridden.
  if (
      endpoint is None
      or endpoint.endswith('gkemulticloud.googleapis.com/')
      or endpoint.endswith('preprod-gkemulticloud.sandbox.googleapis.com/')
  ):
    return 'connectgateway.googleapis.com'
  if 'staging-gkemulticloud' in endpoint:
    return 'staging-connectgateway.sandbox.googleapis.com'
  if endpoint.startswith('http://localhost') or endpoint.endswith(
      'gkemulticloud.sandbox.googleapis.com/'
  ):
    return 'autopush-connectgateway.sandbox.googleapis.com'
  raise errors.UnknownApiEndpointOverrideError('gkemulticloud')


def ExecCredential(expiration_timestamp=None, access_token=None):
  """Generates a Kubernetes execCredential object."""
  return {
      'kind': 'ExecCredential',
      'apiVersion': 'client.authentication.k8s.io/v1',
      'status': {
          'expirationTimestamp': expiration_timestamp,
          'token': access_token,
      },
  }


def _ExecAuthPlugin(cmd_path, cmd_args):
  """Generates and returns an exec auth plugin config.

  Args:
    cmd_path: str, exec command path.
    cmd_args: str, exec command arguments.

  Returns:
    dict, valid exec auth plugin config entry.
  """
  if cmd_path is None:
    bin_name = 'gcloud'
    if platforms.OperatingSystem.IsWindows():
      bin_name = 'gcloud.cmd'
    command = bin_name

    # Check if command is in PATH and executable. Else, print critical(RED)
    # warning as kubectl will break if command is not executable.
    try:
      subprocess.run(
          [command, '--version'],
          timeout=5,
          check=False,
          stdout=subprocess.DEVNULL,
          stderr=subprocess.DEVNULL,
      )
      cmd_path = command
    except Exception:  # pylint: disable=broad-except
      # Provide SDK Full path if command is not in PATH. This helps work
      # around scenarios where cloud-sdk install location is not in PATH
      # as sdk was installed using other distributions methods Eg: brew
      try:
        # config.Paths().sdk_bin_path throws an exception in some test envs,
        # but is commonly defined in prod environments
        sdk_bin_path = config.Paths().sdk_bin_path
        if sdk_bin_path is None:
          log.critical(kubeconfig_util.SDK_BIN_PATH_NOT_FOUND)
          raise kubeconfig_util.Error(kubeconfig_util.SDK_BIN_PATH_NOT_FOUND)
        else:
          sdk_path_bin_name = os.path.join(sdk_bin_path, command)
          subprocess.run(
              [sdk_path_bin_name, '--version'],
              timeout=5,
              check=False,
              stdout=subprocess.DEVNULL,
              stderr=subprocess.DEVNULL,
          )
          # update command if sdk_path_bin_name works
          cmd_path = sdk_path_bin_name
      except Exception:  # pylint: disable=broad-except
        log.critical(kubeconfig_util.SDK_BIN_PATH_NOT_FOUND)

  cfg = {
      'command': cmd_path,
      'apiVersion': 'client.authentication.k8s.io/v1',
      'provideClusterInfo': True,
      'args': cmd_args.split(' '),
      'interactiveMode': 'Never',
  }

  endpoint = properties.VALUES.api_endpoint_overrides.gkemulticloud.Get()
  if endpoint:
    cfg['env'] = [{
        'name': (
            properties.VALUES.api_endpoint_overrides.gkemulticloud.EnvironmentName()
        ),
        'value': endpoint,
    }]
  return cfg


def CheckClusterHasNodePools(cluster_client, cluster_ref):
  """Checks and gives a warning if the cluster does not have a node pool."""
  try:
    if not cluster_client.HasNodePools(cluster_ref):
      log.warning(
          'Cluster does not have a node pool. To use Connect Gateway, '
          'ensure you have at least one Linux node pool running.'
      )
  # pylint: disable=bare-except, this function is just a warning and should not
  # add new failures.
  except:
    pass


def ConnectGatewayInNodePools(cluster, cluster_id):
  version = _GetSemver(cluster, cluster_id)
  return version < semver.SemVer('1.25.0')