1# -*- coding: utf-8 -*- # 2# Copyright 2015 Google LLC. All Rights Reserved. 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15"""Update cluster command.""" 16 17from __future__ import absolute_import 18from __future__ import division 19from __future__ import unicode_literals 20 21from apitools.base.py import exceptions as apitools_exceptions 22 23from googlecloudsdk.api_lib.container import api_adapter 24from googlecloudsdk.api_lib.container import kubeconfig as kconfig 25from googlecloudsdk.api_lib.container import util 26from googlecloudsdk.calliope import actions 27from googlecloudsdk.calliope import arg_parsers 28from googlecloudsdk.calliope import base 29from googlecloudsdk.calliope import exceptions 30from googlecloudsdk.command_lib.container import container_command_util 31from googlecloudsdk.command_lib.container import flags 32from googlecloudsdk.core import log 33from googlecloudsdk.core.console import console_attr 34from googlecloudsdk.core.console import console_io 35from six.moves import input # pylint: disable=redefined-builtin 36 37 38class InvalidAddonValueError(util.Error): 39 """A class for invalid --update-addons input.""" 40 41 def __init__(self, value): 42 message = ('invalid --update-addons value {0}; ' 43 'must be ENABLED or DISABLED.'.format(value)) 44 super(InvalidAddonValueError, self).__init__(message) 45 46 47class InvalidPasswordError(util.Error): 48 """A class for invalid password input.""" 49 50 def __init__(self, value, error): 51 message = 'invalid password value "{0}"; {1}'.format(value, error) 52 super(InvalidPasswordError, self).__init__(message) 53 54 55def _ParseAddonDisabled(val): 56 if val == 'ENABLED': 57 return False 58 if val == 'DISABLED': 59 return True 60 raise InvalidAddonValueError(val) 61 62 63def _AddCommonArgs(parser): 64 """Register common flags for this command. 65 66 Args: 67 parser: An argparse.ArgumentParser-like object. It is mocked out in order to 68 capture some information, but behaves like an ArgumentParser. 69 """ 70 parser.add_argument( 71 'name', metavar='NAME', help='The name of the cluster to update.') 72 parser.add_argument('--node-pool', help='Node pool to be updated.') 73 # Timeout in seconds for the operation, default 3600 seconds (60 minutes) 74 parser.add_argument( 75 '--timeout', 76 type=int, 77 default=3600, 78 hidden=True, 79 help='Timeout (seconds) for waiting on the operation to complete.') 80 flags.AddAsyncFlag(parser) 81 82 83def _AddMutuallyExclusiveArgs(mutex_group, release_track): 84 """Add all arguments that need to be mutually exclusive from each other.""" 85 if release_track == base.ReleaseTrack.ALPHA: 86 mutex_group.add_argument( 87 '--update-addons', 88 type=arg_parsers.ArgDict( 89 spec=dict( 90 { 91 api_adapter.INGRESS: _ParseAddonDisabled, 92 api_adapter.HPA: _ParseAddonDisabled, 93 api_adapter.DASHBOARD: _ParseAddonDisabled, 94 api_adapter.NETWORK_POLICY: _ParseAddonDisabled, 95 api_adapter.ISTIO: _ParseAddonDisabled, 96 api_adapter.APPLICATIONMANAGER: _ParseAddonDisabled, 97 api_adapter.CLOUDBUILD: _ParseAddonDisabled, 98 api_adapter.NODELOCALDNS: _ParseAddonDisabled, 99 api_adapter.GCEPDCSIDRIVER: _ParseAddonDisabled, 100 api_adapter.CONFIGCONNECTOR: _ParseAddonDisabled, 101 }, 102 **{k: _ParseAddonDisabled for k in api_adapter.CLOUDRUN_ADDONS 103 }),), 104 dest='disable_addons', 105 metavar='ADDON=ENABLED|DISABLED', 106 help="""Cluster addons to enable or disable. Options are 107{hpa}=ENABLED|DISABLED 108{ingress}=ENABLED|DISABLED 109{dashboard}=ENABLED|DISABLED 110{istio}=ENABLED|DISABLED 111{application_manager}=ENABLED|DISABLED 112{network_policy}=ENABLED|DISABLED 113{cloudrun}=ENABLED|DISABLED 114{cloudbuild}=ENABLED|DISABLED 115{configconnector}=ENABLED|DISABLED 116{nodelocaldns}=ENABLED|DISABLED 117{gcepdcsidriver}=ENABLED|DISABLED""".format( 118 hpa=api_adapter.HPA, 119 ingress=api_adapter.INGRESS, 120 dashboard=api_adapter.DASHBOARD, 121 network_policy=api_adapter.NETWORK_POLICY, 122 istio=api_adapter.ISTIO, 123 application_manager=api_adapter.APPLICATIONMANAGER, 124 cloudrun=api_adapter.CLOUDRUN_ADDONS[0], 125 cloudbuild=api_adapter.CLOUDBUILD, 126 configconnector=api_adapter.CONFIGCONNECTOR, 127 nodelocaldns=api_adapter.NODELOCALDNS, 128 gcepdcsidriver=api_adapter.GCEPDCSIDRIVER, 129 )) 130 131 elif release_track == base.ReleaseTrack.BETA: 132 mutex_group.add_argument( 133 '--update-addons', 134 type=arg_parsers.ArgDict( 135 spec=dict( 136 { 137 api_adapter.INGRESS: _ParseAddonDisabled, 138 api_adapter.HPA: _ParseAddonDisabled, 139 api_adapter.DASHBOARD: _ParseAddonDisabled, 140 api_adapter.NETWORK_POLICY: _ParseAddonDisabled, 141 api_adapter.ISTIO: _ParseAddonDisabled, 142 api_adapter.APPLICATIONMANAGER: _ParseAddonDisabled, 143 api_adapter.NODELOCALDNS: _ParseAddonDisabled, 144 api_adapter.GCEPDCSIDRIVER: _ParseAddonDisabled, 145 api_adapter.CONFIGCONNECTOR: _ParseAddonDisabled, 146 }, 147 **{k: _ParseAddonDisabled for k in api_adapter.CLOUDRUN_ADDONS 148 }),), 149 dest='disable_addons', 150 metavar='ADDON=ENABLED|DISABLED', 151 help="""Cluster addons to enable or disable. Options are 152{hpa}=ENABLED|DISABLED 153{ingress}=ENABLED|DISABLED 154{dashboard}=ENABLED|DISABLED 155{istio}=ENABLED|DISABLED 156{application_manager}=ENABLED|DISABLED 157{network_policy}=ENABLED|DISABLED 158{cloudrun}=ENABLED|DISABLED 159{configconnector}=ENABLED|DISABLED 160{nodelocaldns}=ENABLED|DISABLED 161{gcepdcsidriver}=ENABLED|DISABLED""".format( 162 hpa=api_adapter.HPA, 163 ingress=api_adapter.INGRESS, 164 dashboard=api_adapter.DASHBOARD, 165 network_policy=api_adapter.NETWORK_POLICY, 166 istio=api_adapter.ISTIO, 167 application_manager=api_adapter.APPLICATIONMANAGER, 168 cloudrun=api_adapter.CLOUDRUN_ADDONS[0], 169 configconnector=api_adapter.CONFIGCONNECTOR, 170 nodelocaldns=api_adapter.NODELOCALDNS, 171 gcepdcsidriver=api_adapter.GCEPDCSIDRIVER, 172 )) 173 174 else: 175 mutex_group.add_argument( 176 '--update-addons', 177 type=arg_parsers.ArgDict( 178 spec=dict( 179 { 180 api_adapter.INGRESS: _ParseAddonDisabled, 181 api_adapter.HPA: _ParseAddonDisabled, 182 api_adapter.DASHBOARD: _ParseAddonDisabled, 183 api_adapter.NETWORK_POLICY: _ParseAddonDisabled, 184 api_adapter.NODELOCALDNS: _ParseAddonDisabled, 185 api_adapter.CONFIGCONNECTOR: _ParseAddonDisabled, 186 api_adapter.GCEPDCSIDRIVER: _ParseAddonDisabled, 187 }, 188 **{k: _ParseAddonDisabled for k in api_adapter.CLOUDRUN_ADDONS 189 }),), 190 dest='disable_addons', 191 metavar='ADDON=ENABLED|DISABLED', 192 help="""Cluster addons to enable or disable. Options are 193{hpa}=ENABLED|DISABLED 194{ingress}=ENABLED|DISABLED 195{dashboard}=ENABLED|DISABLED 196{network_policy}=ENABLED|DISABLED 197{cloudrun}=ENABLED|DISABLED 198{configconnector}=ENABLED|DISABLED 199{nodelocaldns}=ENABLED|DISABLED 200{gcepdcsidriver}=ENABLED|DISABLED""".format( 201 hpa=api_adapter.HPA, 202 ingress=api_adapter.INGRESS, 203 dashboard=api_adapter.DASHBOARD, 204 network_policy=api_adapter.NETWORK_POLICY, 205 cloudrun=api_adapter.CLOUDRUN_ADDONS[0], 206 configconnector=api_adapter.CONFIGCONNECTOR, 207 nodelocaldns=api_adapter.NODELOCALDNS, 208 gcepdcsidriver=api_adapter.GCEPDCSIDRIVER, 209 )) 210 211 mutex_group.add_argument( 212 '--generate-password', 213 action='store_true', 214 default=None, 215 help='Ask the server to generate a secure password and use that as the ' 216 'basic auth password, keeping the existing username.') 217 mutex_group.add_argument( 218 '--set-password', 219 action='store_true', 220 default=None, 221 help='Set the basic auth password to the specified value, keeping the ' 222 'existing username.') 223 224 flags.AddBasicAuthFlags(mutex_group) 225 226 227def _AddAdditionalZonesArg(mutex_group, deprecated=True): 228 action = None 229 if deprecated: 230 action = actions.DeprecationAction( 231 'additional-zones', 232 warn='This flag is deprecated. ' 233 'Use --node-locations=PRIMARY_ZONE,[ZONE,...] instead.') 234 mutex_group.add_argument( 235 '--additional-zones', 236 type=arg_parsers.ArgList(), 237 action=action, 238 metavar='ZONE', 239 help="""\ 240The set of additional zones in which the cluster's node footprint should be 241replicated. All zones must be in the same region as the cluster's primary zone. 242 243Note that the exact same footprint will be replicated in all zones, such that 244if you created a cluster with 4 nodes in a single zone and then use this option 245to spread across 2 more zones, 8 additional nodes will be created. 246 247Multiple locations can be specified, separated by commas. For example: 248 249 $ {command} example-cluster --zone us-central1-a --additional-zones us-central1-b,us-central1-c 250 251To remove all zones other than the cluster's primary zone, pass the empty string 252to the flag. For example: 253 254 $ {command} example-cluster --zone us-central1-a --additional-zones "" 255""") 256 257 258@base.ReleaseTracks(base.ReleaseTrack.GA) 259class Update(base.UpdateCommand): 260 """Update cluster settings for an existing container cluster.""" 261 262 detailed_help = { 263 'DESCRIPTION': 264 '{description}', 265 'EXAMPLES': 266 """\ 267 To enable autoscaling for an existing cluster, run: 268 269 $ {command} sample-cluster --enable-autoscaling 270 """, 271 } 272 273 @staticmethod 274 def Args(parser): 275 """Register flags for this command. 276 277 Args: 278 parser: An argparse.ArgumentParser-like object. It is mocked out in order 279 to capture some information, but behaves like an ArgumentParser. 280 """ 281 _AddCommonArgs(parser) 282 group = parser.add_mutually_exclusive_group(required=True) 283 group_locations = group.add_mutually_exclusive_group() 284 _AddMutuallyExclusiveArgs(group, base.ReleaseTrack.GA) 285 flags.AddNodeLocationsFlag(group_locations) 286 flags.AddClusterAutoscalingFlags(parser, group) 287 flags.AddMasterAuthorizedNetworksFlags( 288 parser, enable_group_for_update=group) 289 flags.AddEnableLegacyAuthorizationFlag(group) 290 flags.AddStartIpRotationFlag(group) 291 flags.AddStartCredentialRotationFlag(group) 292 flags.AddCompleteIpRotationFlag(group) 293 flags.AddCompleteCredentialRotationFlag(group) 294 flags.AddCloudRunConfigFlag(parser) 295 flags.AddUpdateLabelsFlag(group) 296 flags.AddRemoveLabelsFlag(group) 297 flags.AddNetworkPolicyFlags(group) 298 flags.AddEnableIntraNodeVisibilityFlag(group) 299 group_logging_monitoring = group.add_group() 300 flags.AddLoggingServiceFlag(group_logging_monitoring) 301 flags.AddMonitoringServiceFlag(group_logging_monitoring) 302 flags.AddEnableBinAuthzFlag(group) 303 flags.AddEnableStackdriverKubernetesFlag(group) 304 flags.AddDailyMaintenanceWindowFlag(group, add_unset_text=True) 305 flags.AddRecurringMaintenanceWindowFlags(group, is_update=True) 306 flags.AddResourceUsageExportFlags(group, is_update=True) 307 flags.AddReleaseChannelFlag(group, is_update=True, hidden=False) 308 flags.AddWorkloadIdentityFlags(group) 309 flags.AddWorkloadIdentityUpdateFlags(group) 310 flags.AddDatabaseEncryptionFlag(group) 311 flags.AddDisableDatabaseEncryptionFlag(group) 312 flags.AddDisableDefaultSnatFlag(group, for_cluster_create=False) 313 flags.AddVerticalPodAutoscalingFlag(group) 314 flags.AddAutoprovisioningFlags(group) 315 flags.AddEnableShieldedNodesFlags(group) 316 flags.AddMasterGlobalAccessFlag(group, is_update=True) 317 flags.AddPrivateIpv6GoogleAccessTypeFlag('v1', group, hidden=False) 318 flags.AddNotificationConfigFlag(group) 319 320 def ParseUpdateOptions(self, args, locations): 321 get_default = lambda key: getattr(args, key) 322 flags.ValidateNotificationConfigFlag(args) 323 opts = container_command_util.ParseUpdateOptionsBase(args, locations) 324 opts.resource_usage_bigquery_dataset = args.resource_usage_bigquery_dataset 325 opts.clear_resource_usage_bigquery_dataset = \ 326 args.clear_resource_usage_bigquery_dataset 327 opts.enable_network_egress_metering = args.enable_network_egress_metering 328 opts.enable_resource_consumption_metering = \ 329 args.enable_resource_consumption_metering 330 opts.enable_intra_node_visibility = args.enable_intra_node_visibility 331 opts.enable_master_global_access = args.enable_master_global_access 332 opts.enable_shielded_nodes = args.enable_shielded_nodes 333 opts.release_channel = args.release_channel 334 opts.cloud_run_config = flags.GetLegacyCloudRunFlag('{}_config', args, 335 get_default) 336 flags.ValidateCloudRunConfigUpdateArgs(opts.cloud_run_config, 337 args.disable_addons) 338 if args.disable_addons and api_adapter.NODELOCALDNS in args.disable_addons: 339 # NodeLocalDNS is being enabled or disabled 340 console_io.PromptContinue( 341 message='Enabling/Disabling NodeLocal DNSCache causes a re-creation ' 342 'of all cluster nodes at versions 1.15 or above. ' 343 'This operation is long-running and will block other ' 344 'operations on the cluster (including delete) until it has run ' 345 'to completion.', 346 cancel_on_no=True) 347 opts.disable_default_snat = args.disable_default_snat 348 opts.notification_config = args.notification_config 349 return opts 350 351 def Run(self, args): 352 """This is what gets called when the user runs this command. 353 354 Args: 355 args: an argparse namespace. All the arguments that were provided to this 356 command invocation. 357 358 Returns: 359 Some value that we want to have printed later. 360 """ 361 adapter = self.context['api_adapter'] 362 location_get = self.context['location_get'] 363 location = location_get(args) 364 cluster_ref = adapter.ParseCluster(args.name, location) 365 cluster_name = args.name 366 cluster_node_count = None 367 cluster_zone = cluster_ref.zone 368 cluster_is_required = self.IsClusterRequired(args) 369 try: 370 # Attempt to get cluster for better prompts and to validate args. 371 # Error is a warning but not fatal. Should only exit with a failure on 372 # the actual update API calls below. 373 cluster = adapter.GetCluster(cluster_ref) 374 cluster_name = cluster.name 375 cluster_node_count = cluster.currentNodeCount 376 cluster_zone = cluster.zone 377 except (exceptions.HttpException, apitools_exceptions.HttpForbiddenError, 378 util.Error) as error: 379 if cluster_is_required: 380 raise 381 log.warning(('Problem loading details of cluster to update:\n\n{}\n\n' 382 'You can still attempt updates to the cluster.\n').format( 383 console_attr.SafeText(error))) 384 385 # locations will be None if additional-zones was specified, an empty list 386 # if it was specified with no argument, or a populated list if zones were 387 # provided. We want to distinguish between the case where it isn't 388 # specified (and thus shouldn't be passed on to the API) and the case where 389 # it's specified as wanting no additional zones, in which case we must pass 390 # the cluster's primary zone to the API. 391 # TODO(b/29578401): Remove the hasattr once the flag is GA. 392 locations = None 393 if hasattr(args, 'additional_zones') and args.additional_zones is not None: 394 locations = sorted([cluster_ref.zone] + args.additional_zones) 395 if hasattr(args, 'node_locations') and args.node_locations is not None: 396 locations = sorted(args.node_locations) 397 398 flags.LogBasicAuthDeprecationWarning(args) 399 if args.IsSpecified('username') or args.IsSpecified('enable_basic_auth'): 400 flags.MungeBasicAuthFlags(args) 401 options = api_adapter.SetMasterAuthOptions( 402 action=api_adapter.SetMasterAuthOptions.SET_USERNAME, 403 username=args.username, 404 password=args.password) 405 406 try: 407 op_ref = adapter.SetMasterAuth(cluster_ref, options) 408 except apitools_exceptions.HttpError as error: 409 raise exceptions.HttpException(error, util.HTTP_ERROR_FORMAT) 410 elif (args.generate_password or args.set_password or 411 args.IsSpecified('password')): 412 if args.generate_password: 413 password = '' 414 options = api_adapter.SetMasterAuthOptions( 415 action=api_adapter.SetMasterAuthOptions.GENERATE_PASSWORD, 416 password=password) 417 else: 418 password = args.password 419 if not args.IsSpecified('password'): 420 password = input('Please enter the new password:') 421 options = api_adapter.SetMasterAuthOptions( 422 action=api_adapter.SetMasterAuthOptions.SET_PASSWORD, 423 password=password) 424 425 try: 426 op_ref = adapter.SetMasterAuth(cluster_ref, options) 427 del password 428 del options 429 except apitools_exceptions.HttpError as error: 430 del password 431 del options 432 raise exceptions.HttpException(error, util.HTTP_ERROR_FORMAT) 433 elif args.enable_network_policy is not None: 434 console_io.PromptContinue( 435 message='Enabling/Disabling Network Policy causes a rolling ' 436 'update of all cluster nodes, similar to performing a cluster ' 437 'upgrade. This operation is long-running and will block other ' 438 'operations on the cluster (including delete) until it has run ' 439 'to completion.', 440 cancel_on_no=True) 441 options = api_adapter.SetNetworkPolicyOptions( 442 enabled=args.enable_network_policy) 443 try: 444 op_ref = adapter.SetNetworkPolicy(cluster_ref, options) 445 except apitools_exceptions.HttpError as error: 446 raise exceptions.HttpException(error, util.HTTP_ERROR_FORMAT) 447 elif args.start_ip_rotation or args.start_credential_rotation: 448 if args.start_ip_rotation: 449 msg_tmpl = """This will start an IP Rotation on cluster [{name}]. The \ 450master will be updated to serve on a new IP address in addition to the current \ 451IP address. Kubernetes Engine will then recreate all nodes ({num_nodes} nodes) \ 452to point to the new IP address. This operation is long-running and will block \ 453other operations on the cluster (including delete) until it has run to \ 454completion.""" 455 rotate_credentials = False 456 elif args.start_credential_rotation: 457 msg_tmpl = """This will start an IP and Credentials Rotation on cluster\ 458 [{name}]. The master will be updated to serve on a new IP address in addition \ 459to the current IP address, and cluster credentials will be rotated. Kubernetes \ 460Engine will then recreate all nodes ({num_nodes} nodes) to point to the new IP \ 461address. This operation is long-running and will block other operations on the \ 462cluster (including delete) until it has run to completion.""" 463 rotate_credentials = True 464 console_io.PromptContinue( 465 message=msg_tmpl.format( 466 name=cluster_name, 467 num_nodes=cluster_node_count if cluster_node_count else '?'), 468 cancel_on_no=True) 469 try: 470 op_ref = adapter.StartIpRotation( 471 cluster_ref, rotate_credentials=rotate_credentials) 472 except apitools_exceptions.HttpError as error: 473 raise exceptions.HttpException(error, util.HTTP_ERROR_FORMAT) 474 elif args.complete_ip_rotation or args.complete_credential_rotation: 475 if args.complete_ip_rotation: 476 msg_tmpl = """This will complete the in-progress IP Rotation on \ 477cluster [{name}]. The master will be updated to stop serving on the old IP \ 478address and only serve on the new IP address. Make sure all API clients have \ 479been updated to communicate with the new IP address (e.g. by running `gcloud \ 480container clusters get-credentials --project {project} --zone {zone} {name}`). \ 481This operation is long-running and will block other operations on the cluster \ 482(including delete) until it has run to completion.""" 483 elif args.complete_credential_rotation: 484 msg_tmpl = """This will complete the in-progress Credential Rotation on\ 485 cluster [{name}]. The master will be updated to stop serving on the old IP \ 486address and only serve on the new IP address. Old cluster credentials will be \ 487invalidated. Make sure all API clients have been updated to communicate with \ 488the new IP address (e.g. by running `gcloud container clusters get-credentials \ 489--project {project} --zone {zone} {name}`). This operation is long-running and \ 490will block other operations on the cluster (including delete) until it has run \ 491to completion.""" 492 console_io.PromptContinue( 493 message=msg_tmpl.format( 494 name=cluster_name, 495 project=cluster_ref.projectId, 496 zone=cluster_zone), 497 cancel_on_no=True) 498 try: 499 op_ref = adapter.CompleteIpRotation(cluster_ref) 500 except apitools_exceptions.HttpError as error: 501 raise exceptions.HttpException(error, util.HTTP_ERROR_FORMAT) 502 elif args.update_labels is not None: 503 try: 504 op_ref = adapter.UpdateLabels(cluster_ref, args.update_labels) 505 except apitools_exceptions.HttpError as error: 506 raise exceptions.HttpException(error, util.HTTP_ERROR_FORMAT) 507 elif args.remove_labels is not None: 508 try: 509 op_ref = adapter.RemoveLabels(cluster_ref, args.remove_labels) 510 except apitools_exceptions.HttpError as error: 511 raise exceptions.HttpException(error, util.HTTP_ERROR_FORMAT) 512 elif args.logging_service is not None and args.monitoring_service is None: 513 try: 514 op_ref = adapter.SetLoggingService(cluster_ref, args.logging_service) 515 except apitools_exceptions.HttpError as error: 516 raise exceptions.HttpException(error, util.HTTP_ERROR_FORMAT) 517 elif args.maintenance_window is not None: 518 try: 519 op_ref = adapter.SetDailyMaintenanceWindow(cluster_ref, 520 cluster.maintenancePolicy, 521 args.maintenance_window) 522 except apitools_exceptions.HttpError as error: 523 raise exceptions.HttpException(error, util.HTTP_ERROR_FORMAT) 524 elif getattr(args, 'maintenance_window_start', None) is not None: 525 try: 526 op_ref = adapter.SetRecurringMaintenanceWindow( 527 cluster_ref, cluster.maintenancePolicy, 528 args.maintenance_window_start, args.maintenance_window_end, 529 args.maintenance_window_recurrence) 530 except apitools_exceptions.HttpError as error: 531 raise exceptions.HttpException(error, util.HTTP_ERROR_FORMAT) 532 elif getattr(args, 'clear_maintenance_window', None): 533 try: 534 op_ref = adapter.RemoveMaintenanceWindow(cluster_ref, 535 cluster.maintenancePolicy) 536 except apitools_exceptions.HttpError as error: 537 raise exceptions.HttpException(error, util.HTTP_ERROR_FORMAT) 538 elif getattr(args, 'add_maintenance_exclusion_end', None) is not None: 539 try: 540 op_ref = adapter.AddMaintenanceExclusion( 541 cluster_ref, cluster.maintenancePolicy, 542 args.add_maintenance_exclusion_name, 543 args.add_maintenance_exclusion_start, 544 args.add_maintenance_exclusion_end) 545 except apitools_exceptions.HttpError as error: 546 raise exceptions.HttpException(error, util.HTTP_ERROR_FORMAT) 547 elif getattr(args, 'remove_maintenance_exclusion', None) is not None: 548 try: 549 op_ref = adapter.RemoveMaintenanceExclusion( 550 cluster_ref, cluster.maintenancePolicy, 551 args.remove_maintenance_exclusion) 552 except apitools_exceptions.HttpError as error: 553 raise exceptions.HttpException(error, util.HTTP_ERROR_FORMAT) 554 elif getattr(args, 'add_cross_connect_subnetworks', None) is not None: 555 try: 556 op_ref = adapter.ModifyCrossConnectSubnetworks( 557 cluster_ref, 558 cluster.privateClusterConfig.crossConnectConfig, 559 add_subnetworks=args.add_cross_connect_subnetworks) 560 except apitools_exceptions.HttpError as error: 561 raise exceptions.HttpException(error, util.HTTP_ERROR_FORMAT) 562 elif getattr(args, 'remove_cross_connect_subnetworks', None) is not None: 563 try: 564 op_ref = adapter.ModifyCrossConnectSubnetworks( 565 cluster_ref, 566 cluster.privateClusterConfig.crossConnectConfig, 567 remove_subnetworks=args.remove_cross_connect_subnetworks) 568 569 except apitools_exceptions.HttpError as error: 570 raise exceptions.HttpException(error, util.HTTP_ERROR_FORMAT) 571 elif getattr(args, 'clear_cross_connect_subnetworks', None) is not None: 572 try: 573 op_ref = adapter.ModifyCrossConnectSubnetworks( 574 cluster_ref, 575 cluster.privateClusterConfig.crossConnectConfig, 576 clear_all_subnetworks=True) 577 except apitools_exceptions.HttpError as error: 578 raise exceptions.HttpException(error, util.HTTP_ERROR_FORMAT) 579 else: 580 if args.enable_legacy_authorization is not None: 581 op_ref = adapter.SetLegacyAuthorization( 582 cluster_ref, args.enable_legacy_authorization) 583 else: 584 options = self.ParseUpdateOptions(args, locations) 585 op_ref = adapter.UpdateCluster(cluster_ref, options) 586 587 if not args.async_: 588 adapter.WaitForOperation( 589 op_ref, 590 'Updating {0}'.format(cluster_ref.clusterId), 591 timeout_s=args.timeout) 592 593 log.UpdatedResource(cluster_ref) 594 cluster_url = util.GenerateClusterUrl(cluster_ref) 595 log.status.Print('To inspect the contents of your cluster, go to: ' + 596 cluster_url) 597 598 if (args.start_ip_rotation or args.complete_ip_rotation or 599 args.start_credential_rotation or args.complete_credential_rotation): 600 cluster = adapter.GetCluster(cluster_ref) 601 try: 602 util.ClusterConfig.Persist(cluster, cluster_ref.projectId) 603 except kconfig.MissingEnvVarError as error: 604 log.warning(error) 605 606 def IsClusterRequired(self, args): 607 """Returns if failure getting the cluster should be an error.""" 608 return bool( 609 getattr(args, 'maintenance_window_end', False) or 610 getattr(args, 'clear_maintenance_window', False) or 611 getattr(args, 'add_maintenance_exclusion_end', False) or 612 getattr(args, 'remove_maintenance_exclusion', False) or 613 getattr(args, 'add_cross_connect_subnetworks', False) or 614 getattr(args, 'remove_cross_connect_subnetworks', False) or 615 getattr(args, 'clear_cross_connect_subnetworks', False)) 616 617 618@base.ReleaseTracks(base.ReleaseTrack.BETA) 619class UpdateBeta(Update): 620 """Update cluster settings for an existing container cluster.""" 621 622 @staticmethod 623 def Args(parser): 624 _AddCommonArgs(parser) 625 group = parser.add_mutually_exclusive_group(required=True) 626 _AddMutuallyExclusiveArgs(group, base.ReleaseTrack.BETA) 627 flags.AddClusterAutoscalingFlags(parser, group) 628 group_locations = group.add_mutually_exclusive_group() 629 _AddAdditionalZonesArg(group_locations, deprecated=True) 630 flags.AddNodeLocationsFlag(group_locations) 631 group_logging_monitoring = group.add_group() 632 flags.AddLoggingServiceFlag(group_logging_monitoring) 633 flags.AddMonitoringServiceFlag(group_logging_monitoring) 634 flags.AddEnableStackdriverKubernetesFlag(group) 635 flags.AddEnableLoggingMonitoringSystemOnlyFlag(group) 636 flags.AddEnableWorkloadMonitoringEapFlag(group) 637 flags.AddEnableMasterSignalsFlags(group) 638 flags.AddMasterAuthorizedNetworksFlags( 639 parser, enable_group_for_update=group) 640 flags.AddEnableLegacyAuthorizationFlag(group) 641 flags.AddStartIpRotationFlag(group) 642 flags.AddStartCredentialRotationFlag(group) 643 flags.AddCompleteIpRotationFlag(group) 644 flags.AddCompleteCredentialRotationFlag(group) 645 flags.AddUpdateLabelsFlag(group) 646 flags.AddRemoveLabelsFlag(group) 647 flags.AddNetworkPolicyFlags(group) 648 flags.AddDailyMaintenanceWindowFlag(group, add_unset_text=True) 649 flags.AddRecurringMaintenanceWindowFlags(group, is_update=True) 650 flags.AddPodSecurityPolicyFlag(group) 651 flags.AddEnableBinAuthzFlag(group) 652 flags.AddAutoprovisioningFlags(group) 653 flags.AddAutoscalingProfilesFlag(group) 654 flags.AddVerticalPodAutoscalingFlag(group) 655 flags.AddResourceUsageExportFlags(group, is_update=True) 656 flags.AddIstioConfigFlag(parser) 657 flags.AddCloudRunConfigFlag(parser) 658 flags.AddEnableIntraNodeVisibilityFlag(group) 659 flags.AddWorkloadIdentityFlags( 660 group, use_identity_provider=True, use_workload_certificates=True) 661 flags.AddWorkloadIdentityUpdateFlags(group, use_workload_certificates=True) 662 flags.AddGkeOidcFlag(group) 663 flags.AddDatabaseEncryptionFlag(group) 664 flags.AddDisableDatabaseEncryptionFlag(group) 665 flags.AddReleaseChannelFlag(group, is_update=True, hidden=False) 666 flags.AddEnableShieldedNodesFlags(group) 667 flags.AddTpuFlags(group, enable_tpu_service_networking=True) 668 flags.AddMasterGlobalAccessFlag(group, is_update=True) 669 flags.AddEnableGvnicFlag(group) 670 flags.AddDisableDefaultSnatFlag(group, for_cluster_create=False) 671 flags.AddNotificationConfigFlag(group) 672 flags.AddPrivateIpv6GoogleAccessTypeFlag('v1beta1', group, hidden=False) 673 flags.AddKubernetesObjectsExportConfig(group) 674 flags.AddDisableAutopilotFlag(group, hidden=True) 675 flags.AddILBSubsettingFlags(group, hidden=True) 676 flags.AddClusterDNSFlags(group, hidden=True) 677 flags.AddCrossConnectSubnetworksMutationFlags(group) 678 679 def ParseUpdateOptions(self, args, locations): 680 get_default = lambda key: getattr(args, key) 681 flags.ValidateNotificationConfigFlag(args) 682 opts = container_command_util.ParseUpdateOptionsBase(args, locations) 683 opts.enable_pod_security_policy = args.enable_pod_security_policy 684 opts.istio_config = args.istio_config 685 opts.cloud_run_config = flags.GetLegacyCloudRunFlag('{}_config', args, 686 get_default) 687 opts.resource_usage_bigquery_dataset = args.resource_usage_bigquery_dataset 688 opts.enable_intra_node_visibility = args.enable_intra_node_visibility 689 opts.clear_resource_usage_bigquery_dataset = \ 690 args.clear_resource_usage_bigquery_dataset 691 opts.enable_network_egress_metering = args.enable_network_egress_metering 692 opts.enable_resource_consumption_metering = args.enable_resource_consumption_metering 693 opts.workload_identity_certificate_authority = args.workload_identity_certificate_authority 694 opts.disable_workload_identity_certificates = args.disable_workload_identity_certificates 695 flags.ValidateIstioConfigUpdateArgs(args.istio_config, args.disable_addons) 696 flags.ValidateCloudRunConfigUpdateArgs(opts.cloud_run_config, 697 args.disable_addons) 698 if args.disable_addons and api_adapter.NODELOCALDNS in args.disable_addons: 699 # NodeLocalDNS is being enabled or disabled 700 console_io.PromptContinue( 701 message='Enabling/Disabling NodeLocal DNSCache causes a re-creation ' 702 'of all cluster nodes at versions 1.15 or above. ' 703 'This operation is long-running and will block other ' 704 'operations on the cluster (including delete) until it has run ' 705 'to completion.', 706 cancel_on_no=True) 707 708 opts.enable_stackdriver_kubernetes = args.enable_stackdriver_kubernetes 709 opts.enable_logging_monitoring_system_only = args.enable_logging_monitoring_system_only 710 opts.master_logs = args.master_logs 711 opts.no_master_logs = args.no_master_logs 712 opts.enable_master_metrics = args.enable_master_metrics 713 opts.release_channel = args.release_channel 714 opts.autoscaling_profile = args.autoscaling_profile 715 716 # Top-level update options are automatically forced to be 717 # mutually-exclusive, so we don't need special handling for these two. 718 opts.identity_provider = args.identity_provider 719 opts.enable_shielded_nodes = args.enable_shielded_nodes 720 opts.enable_tpu = args.enable_tpu 721 opts.tpu_ipv4_cidr = args.tpu_ipv4_cidr 722 opts.enable_tpu_service_networking = args.enable_tpu_service_networking 723 opts.enable_master_global_access = args.enable_master_global_access 724 opts.enable_gvnic = args.enable_gvnic 725 opts.disable_default_snat = args.disable_default_snat 726 opts.notification_config = args.notification_config 727 opts.kubernetes_objects_changes_target = args.kubernetes_objects_changes_target 728 opts.kubernetes_objects_snapshots_target = args.kubernetes_objects_snapshots_target 729 opts.enable_gke_oidc = args.enable_gke_oidc 730 opts.enable_workload_monitoring_eap = args.enable_workload_monitoring_eap 731 opts.disable_autopilot = args.disable_autopilot 732 opts.enable_l4_ilb_subsetting = args.enable_l4_ilb_subsetting 733 opts.cluster_dns = args.cluster_dns 734 opts.cluster_dns_scope = args.cluster_dns_scope 735 opts.cluster_dns_domain = args.cluster_dns_domain 736 if opts.cluster_dns and opts.cluster_dns.lower() == 'clouddns': 737 console_io.PromptContinue( 738 message='Enabling CloudDNS is a one-way operation. Once enabled, ' 739 'this configuration cannot be disabled. ' 740 'All the node-pools in the cluster need to be re-created by the user ' 741 'to start using CloudDNS for DNS lookups. It is highly recommended to' 742 ' complete this step shortly after enabling CloudDNS.', 743 cancel_on_no=True) 744 return opts 745 746 747@base.ReleaseTracks(base.ReleaseTrack.ALPHA) 748class UpdateAlpha(Update): 749 """Update cluster settings for an existing container cluster.""" 750 751 @staticmethod 752 def Args(parser): 753 _AddCommonArgs(parser) 754 group = parser.add_mutually_exclusive_group(required=True) 755 _AddMutuallyExclusiveArgs(group, base.ReleaseTrack.ALPHA) 756 flags.AddClusterAutoscalingFlags(parser, group) 757 group_locations = group.add_mutually_exclusive_group() 758 _AddAdditionalZonesArg(group_locations, deprecated=True) 759 flags.AddNodeLocationsFlag(group_locations) 760 group_logging_monitoring = group.add_group() 761 flags.AddLoggingServiceFlag(group_logging_monitoring) 762 flags.AddMonitoringServiceFlag(group_logging_monitoring) 763 flags.AddEnableStackdriverKubernetesFlag(group) 764 flags.AddEnableLoggingMonitoringSystemOnlyFlag(group) 765 flags.AddEnableWorkloadMonitoringEapFlag(group) 766 flags.AddEnableMasterSignalsFlags(group) 767 flags.AddMasterAuthorizedNetworksFlags( 768 parser, enable_group_for_update=group) 769 flags.AddEnableLegacyAuthorizationFlag(group) 770 flags.AddStartIpRotationFlag(group) 771 flags.AddStartCredentialRotationFlag(group) 772 flags.AddCompleteIpRotationFlag(group) 773 flags.AddCompleteCredentialRotationFlag(group) 774 flags.AddUpdateLabelsFlag(group) 775 flags.AddRemoveLabelsFlag(group) 776 flags.AddNetworkPolicyFlags(group) 777 flags.AddAutoprovisioningFlags(group, hidden=False) 778 flags.AddAutoscalingProfilesFlag(group) 779 flags.AddDailyMaintenanceWindowFlag(group, add_unset_text=True) 780 flags.AddRecurringMaintenanceWindowFlags(group, is_update=True) 781 flags.AddPodSecurityPolicyFlag(group) 782 flags.AddEnableBinAuthzFlag(group) 783 flags.AddResourceUsageExportFlags(group, is_update=True) 784 flags.AddVerticalPodAutoscalingFlag(group) 785 flags.AddSecurityProfileForUpdateFlag(group) 786 flags.AddIstioConfigFlag(parser) 787 flags.AddCloudRunConfigFlag(parser) 788 flags.AddEnableIntraNodeVisibilityFlag(group) 789 flags.AddWorkloadIdentityFlags( 790 group, use_identity_provider=True, use_workload_certificates=True) 791 flags.AddWorkloadIdentityUpdateFlags(group, use_workload_certificates=True) 792 flags.AddGkeOidcFlag(group) 793 flags.AddDisableDefaultSnatFlag(group, for_cluster_create=False) 794 flags.AddDatabaseEncryptionFlag(group) 795 flags.AddDisableDatabaseEncryptionFlag(group) 796 flags.AddCostManagementConfigFlag(group, is_update=True) 797 flags.AddReleaseChannelFlag(group, is_update=True, hidden=False) 798 flags.AddEnableShieldedNodesFlags(group) 799 flags.AddTpuFlags(group, enable_tpu_service_networking=True) 800 flags.AddMasterGlobalAccessFlag(group, is_update=True) 801 flags.AddEnableGvnicFlag(group) 802 flags.AddNotificationConfigFlag(group) 803 flags.AddPrivateIpv6GoogleAccessTypeFlag('v1alpha1', group, hidden=False) 804 flags.AddKubernetesObjectsExportConfig(group) 805 flags.AddDisableAutopilotFlag(group, hidden=True) 806 flags.AddILBSubsettingFlags(group, hidden=True) 807 flags.AddClusterDNSFlags(group, hidden=True) 808 flags.AddCrossConnectSubnetworksMutationFlags(group) 809 810 def ParseUpdateOptions(self, args, locations): 811 get_default = lambda key: getattr(args, key) 812 flags.ValidateNotificationConfigFlag(args) 813 opts = container_command_util.ParseUpdateOptionsBase(args, locations) 814 opts.autoscaling_profile = args.autoscaling_profile 815 opts.enable_pod_security_policy = args.enable_pod_security_policy 816 opts.resource_usage_bigquery_dataset = args.resource_usage_bigquery_dataset 817 opts.clear_resource_usage_bigquery_dataset = \ 818 args.clear_resource_usage_bigquery_dataset 819 opts.security_profile = args.security_profile 820 opts.istio_config = args.istio_config 821 opts.cloud_run_config = flags.GetLegacyCloudRunFlag('{}_config', args, 822 get_default) 823 opts.enable_intra_node_visibility = args.enable_intra_node_visibility 824 opts.enable_network_egress_metering = args.enable_network_egress_metering 825 opts.enable_resource_consumption_metering = args.enable_resource_consumption_metering 826 opts.workload_identity_certificate_authority = args.workload_identity_certificate_authority 827 opts.disable_workload_identity_certificates = args.disable_workload_identity_certificates 828 flags.ValidateIstioConfigUpdateArgs(args.istio_config, args.disable_addons) 829 flags.ValidateCloudRunConfigUpdateArgs(opts.cloud_run_config, 830 args.disable_addons) 831 if args.disable_addons and api_adapter.NODELOCALDNS in args.disable_addons: 832 # NodeLocalDNS is being enabled or disabled 833 console_io.PromptContinue( 834 message='Enabling/Disabling NodeLocal DNSCache causes a re-creation ' 835 'of all cluster nodes at versions 1.15 or above. ' 836 'This operation is long-running and will block other ' 837 'operations on the cluster (including delete) until it has run ' 838 'to completion.', 839 cancel_on_no=True) 840 opts.enable_stackdriver_kubernetes = args.enable_stackdriver_kubernetes 841 opts.enable_logging_monitoring_system_only = args.enable_logging_monitoring_system_only 842 opts.no_master_logs = args.no_master_logs 843 opts.master_logs = args.master_logs 844 opts.enable_master_metrics = args.enable_master_metrics 845 opts.release_channel = args.release_channel 846 opts.enable_tpu = args.enable_tpu 847 opts.tpu_ipv4_cidr = args.tpu_ipv4_cidr 848 opts.enable_tpu_service_networking = args.enable_tpu_service_networking 849 850 # Top-level update options are automatically forced to be 851 # mutually-exclusive, so we don't need special handling for these two. 852 opts.identity_provider = args.identity_provider 853 opts.enable_shielded_nodes = args.enable_shielded_nodes 854 opts.disable_default_snat = args.disable_default_snat 855 opts.enable_cost_management = args.enable_cost_management 856 opts.enable_master_global_access = args.enable_master_global_access 857 opts.enable_gvnic = args.enable_gvnic 858 opts.notification_config = args.notification_config 859 opts.kubernetes_objects_changes_target = args.kubernetes_objects_changes_target 860 opts.kubernetes_objects_snapshots_target = args.kubernetes_objects_snapshots_target 861 opts.enable_gke_oidc = args.enable_gke_oidc 862 opts.enable_workload_monitoring_eap = args.enable_workload_monitoring_eap 863 opts.disable_autopilot = args.disable_autopilot 864 opts.enable_l4_ilb_subsetting = args.enable_l4_ilb_subsetting 865 opts.cluster_dns = args.cluster_dns 866 opts.cluster_dns_scope = args.cluster_dns_scope 867 opts.cluster_dns_domain = args.cluster_dns_domain 868 if opts.cluster_dns and opts.cluster_dns.lower() == 'clouddns': 869 console_io.PromptContinue( 870 message='Enabling CloudDNS is a one-way operation. Once enabled, ' 871 'this configuration cannot be disabled.' 872 'All the node-pools in the cluster need to be re-created by the user ' 873 'to start using CloudDNS for DNS lookups. It is highly recommended to' 874 ' complete this step shortly after enabling CloudDNS.', 875 cancel_on_no=True) 876 877 return opts 878