218 lines
9.1 KiB
Python
218 lines
9.1 KiB
Python
# -*- 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.
|
|
"""Command to register an Attached cluster with the fleet.
|
|
|
|
This command performs the full end-to-end steps required to attach a cluster.
|
|
"""
|
|
from __future__ import absolute_import
|
|
from __future__ import division
|
|
from __future__ import unicode_literals
|
|
|
|
import json
|
|
|
|
from googlecloudsdk.api_lib.container.gkemulticloud import attached as api_util
|
|
from googlecloudsdk.api_lib.container.gkemulticloud import locations as loc_util
|
|
from googlecloudsdk.calliope import base
|
|
from googlecloudsdk.command_lib.container.attached import cluster_util
|
|
from googlecloudsdk.command_lib.container.attached import flags as attached_flags
|
|
from googlecloudsdk.command_lib.container.attached import resource_args
|
|
from googlecloudsdk.command_lib.container.fleet import kube_util
|
|
from googlecloudsdk.command_lib.container.gkemulticloud import command_util
|
|
from googlecloudsdk.command_lib.container.gkemulticloud import constants
|
|
from googlecloudsdk.command_lib.container.gkemulticloud import endpoint_util
|
|
from googlecloudsdk.command_lib.container.gkemulticloud import errors
|
|
from googlecloudsdk.command_lib.container.gkemulticloud import flags
|
|
from googlecloudsdk.command_lib.run import exceptions as run_exceptions
|
|
from googlecloudsdk.command_lib.run import pretty_print
|
|
from googlecloudsdk.core import exceptions
|
|
from googlecloudsdk.core.console import console_io
|
|
from googlecloudsdk.core.util import retry
|
|
import six
|
|
|
|
# Command needs to be in one line for the docgen tool to format properly.
|
|
_EXAMPLES = """
|
|
Register a cluster to a fleet.
|
|
|
|
To register a cluster with a private OIDC issuer, run:
|
|
|
|
$ {command} my-cluster --location=us-west1 --platform-version=PLATFORM_VERSION --fleet-project=FLEET_PROJECT_NUM --distribution=DISTRIBUTION --context=CLUSTER_CONTEXT --has-private-issuer
|
|
|
|
To register a cluster with a public OIDC issuer, run:
|
|
|
|
$ {command} my-cluster --location=us-west1 --platform-version=PLATFORM_VERSION --fleet-project=FLEET_PROJECT_NUM --distribution=DISTRIBUTION --context=CLUSTER_CONTEXT --issuer-url=https://ISSUER_URL
|
|
|
|
To specify a kubeconfig file, run:
|
|
|
|
$ {command} my-cluster --location=us-west1 --platform-version=PLATFORM_VERSION --fleet-project=FLEET_PROJECT_NUM --distribution=DISTRIBUTION --context=CLUSTER_CONTEXT --has-private-issuer --kubeconfig=KUBECONFIG_PATH
|
|
|
|
To register and set cluster admin users, run:
|
|
|
|
$ {command} my-cluster --location=us-west1 --platform-version=PLATFORM_VERSION --fleet-project=FLEET_PROJECT_NUM --distribution=DISTRIBUTION --context=CLUSTER_CONTEXT --issuer-url=https://ISSUER_URL --admin-users=USER1,USER2
|
|
|
|
To specify custom tolerations and labels for system component pods, run:
|
|
|
|
$ {command} my-cluster --location=us-west1 --platform-version=PLATFORM_VERSION --fleet-project=FLEET_PROJECT_NUM --distribution=DISTRIBUTION --context=CLUSTER_CONTEXT --system-component-tolerations=TOLERATIONS --system-component-labels=LABELS
|
|
|
|
where TOLERATIONS have the format:
|
|
key=value:Effect:NoSchedule (examples: key1=value1:Equal:NoSchedule,key2:Exists:PreferNoSchedule, :Exists:NoExecute)
|
|
and LABELS have the format:
|
|
key=value (examples: key1=value1,key2="")
|
|
"""
|
|
|
|
|
|
@base.ReleaseTracks(base.ReleaseTrack.ALPHA, base.ReleaseTrack.GA)
|
|
@base.DefaultUniverseOnly
|
|
class Register(base.CreateCommand):
|
|
"""Register an Attached cluster."""
|
|
|
|
detailed_help = {'EXAMPLES': _EXAMPLES}
|
|
|
|
@staticmethod
|
|
def Args(parser):
|
|
"""Registers flags for this command."""
|
|
resource_args.AddAttachedClusterResourceArg(parser, 'to register')
|
|
|
|
attached_flags.AddPlatformVersion(parser)
|
|
attached_flags.AddRegisterOidcConfig(parser)
|
|
attached_flags.AddDistribution(parser, required=True)
|
|
attached_flags.AddAdminUsers(parser)
|
|
attached_flags.AddKubectl(parser)
|
|
attached_flags.AddProxyConfig(parser)
|
|
attached_flags.AddSkipClusterAdminCheck(parser)
|
|
attached_flags.AddSystemComponentTolerations(parser)
|
|
attached_flags.AddSystemComponentLabels(parser)
|
|
|
|
flags.AddAnnotations(parser)
|
|
flags.AddValidateOnly(parser, 'cluster to create')
|
|
flags.AddFleetProject(parser)
|
|
flags.AddDescription(parser)
|
|
flags.AddLogging(parser, True)
|
|
flags.AddMonitoringConfig(parser, True, True)
|
|
flags.AddBinauthzEvaluationMode(parser)
|
|
flags.AddAdminGroups(parser)
|
|
flags.AddWorkloadVulnerabilityScanning(parser)
|
|
flags.AddResourceManagerTags(parser)
|
|
|
|
parser.display_info.AddFormat(constants.ATTACHED_CLUSTERS_FORMAT)
|
|
|
|
def Run(self, args):
|
|
location = resource_args.ParseAttachedClusterResourceArg(args).locationsId
|
|
if (
|
|
attached_flags.GetHasPrivateIssuer(args)
|
|
and attached_flags.GetDistribution(args) == 'eks'
|
|
):
|
|
raise run_exceptions.ArgumentError(
|
|
'Distributions of type "eks" cannot use the `has-private-issuer`'
|
|
' flag.'
|
|
)
|
|
# Validate system component tolerations early to fail fast.
|
|
attached_flags.GetSystemComponentTolerations(args)
|
|
with endpoint_util.GkemulticloudEndpointOverride(location):
|
|
cluster_ref = resource_args.ParseAttachedClusterResourceArg(args)
|
|
manifest = self._get_manifest(args, cluster_ref)
|
|
|
|
with kube_util.KubernetesClient(
|
|
kubeconfig=attached_flags.GetKubeconfig(args),
|
|
context=attached_flags.GetContext(args),
|
|
enable_workload_identity=True,
|
|
) as kube_client:
|
|
if not attached_flags.GetSkipClusterAdminCheck(args):
|
|
kube_client.CheckClusterAdminPermissions()
|
|
|
|
if attached_flags.GetHasPrivateIssuer(args):
|
|
pretty_print.Info('Fetching cluster OIDC information')
|
|
issuer_url, jwks = self._get_authority(kube_client)
|
|
setattr(args, 'issuer_url', issuer_url)
|
|
setattr(args, 'oidc_jwks', jwks)
|
|
|
|
try:
|
|
if not flags.GetValidateOnly(args):
|
|
pretty_print.Info('Creating in-cluster install agent')
|
|
kube_client.Apply(manifest)
|
|
retryer = retry.Retryer(
|
|
max_retrials=constants.ATTACHED_INSTALL_AGENT_VERIFY_RETRIES
|
|
)
|
|
retryer.RetryOnException(
|
|
cluster_util.verify_install_agent_deployed,
|
|
args=(kube_client,),
|
|
sleep_ms=constants.ATTACHED_INSTALL_AGENT_VERIFY_WAIT_MS,
|
|
)
|
|
|
|
create_resp = self._create_attached_cluster(args, cluster_ref)
|
|
except retry.RetryException as e:
|
|
self._remove_manifest(args, kube_client, manifest)
|
|
# last_result[1] holds information about the last exception the
|
|
# retryer caught. last_result[1][1] holds the exception type and
|
|
# last_result[1][2] holds the exception value. The retry exception is
|
|
# not useful to users, so reraise whatever error caused it to timeout.
|
|
if e.last_result[1]:
|
|
exceptions.reraise(e.last_result[1][1], e.last_result[1][2])
|
|
raise
|
|
except console_io.OperationCancelledError:
|
|
msg = """To manually clean up the in-cluster install agent, run:
|
|
|
|
$ gcloud container attached clusters generate-install-manifest --location={} --platform-version={} --format="value(manifest)" {} | kubectl delete -f -
|
|
|
|
AFTER the attach operation completes.
|
|
""".format(
|
|
location,
|
|
attached_flags.GetPlatformVersion(args),
|
|
cluster_ref.attachedClustersId,
|
|
)
|
|
pretty_print.Info(msg)
|
|
raise
|
|
except: # pylint: disable=broad-except
|
|
self._remove_manifest(args, kube_client, manifest)
|
|
raise
|
|
|
|
self._remove_manifest(args, kube_client, manifest)
|
|
|
|
return create_resp
|
|
|
|
def _get_manifest(self, args, cluster_ref):
|
|
location_client = loc_util.LocationsClient()
|
|
resp = location_client.GenerateInstallManifest(cluster_ref, args=args)
|
|
return resp.manifest
|
|
|
|
def _remove_manifest(self, args, kube_client, manifest):
|
|
if not flags.GetValidateOnly(args):
|
|
pretty_print.Info('Deleting in-cluster install agent')
|
|
kube_client.Delete(manifest)
|
|
|
|
def _get_authority(self, kube_client):
|
|
openid_config_json = six.ensure_str(
|
|
kube_client.GetOpenIDConfiguration(), encoding='utf-8'
|
|
)
|
|
issuer_url = json.loads(openid_config_json).get('issuer')
|
|
if not issuer_url:
|
|
raise errors.MissingOIDCIssuerURL(openid_config_json)
|
|
jwks = kube_client.GetOpenIDKeyset()
|
|
return issuer_url, jwks
|
|
|
|
def _create_attached_cluster(self, args, cluster_ref):
|
|
cluster_client = api_util.ClustersClient()
|
|
message = command_util.ClusterMessage(
|
|
cluster_ref.attachedClustersId,
|
|
action='Creating',
|
|
kind=constants.ATTACHED,
|
|
)
|
|
return command_util.Create(
|
|
resource_ref=cluster_ref,
|
|
resource_client=cluster_client,
|
|
args=args,
|
|
message=message,
|
|
kind=constants.ATTACHED_CLUSTER_KIND,
|
|
)
|