# -*- coding: utf-8 -*- # # Copyright 2017 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 for creating VM instances running Docker images.""" from __future__ import absolute_import from __future__ import division from __future__ import unicode_literals from googlecloudsdk.api_lib.compute import base_classes from googlecloudsdk.api_lib.compute import containers_utils from googlecloudsdk.api_lib.compute import image_utils from googlecloudsdk.api_lib.compute import instance_utils from googlecloudsdk.api_lib.compute import metadata_utils from googlecloudsdk.api_lib.compute import utils from googlecloudsdk.api_lib.compute.instances.create import utils as create_utils from googlecloudsdk.calliope import base from googlecloudsdk.calliope import exceptions from googlecloudsdk.command_lib.compute import completers from googlecloudsdk.command_lib.compute import scope as compute_scopes from googlecloudsdk.command_lib.compute.instances import flags as instances_flags from googlecloudsdk.command_lib.compute.resource_policies import flags as maintenance_flags from googlecloudsdk.command_lib.compute.resource_policies import util as maintenance_util from googlecloudsdk.command_lib.util.args import labels_util from googlecloudsdk.core import log def _Args( parser, deprecate_maintenance_policy=False, container_mount_enabled=False, support_multi_writer=True, support_confidential_compute_type=False, support_confidential_compute_type_tdx=False, support_snp_svsm=False, support_specific_then_x_affinity=False, support_disk_labels=False, support_ipv6_only=False, support_graceful_shutdown=False, support_flex_start=True, support_skip_guest_os_shutdown=False, support_workload_identity_config=False, ): """Add flags shared by all release tracks.""" parser.display_info.AddFormat(instances_flags.DEFAULT_LIST_FORMAT) metadata_utils.AddMetadataArgs(parser) instances_flags.AddDiskArgs( parser, True, container_mount_enabled=container_mount_enabled ) instances_flags.AddCreateDiskArgs( parser, container_mount_enabled=container_mount_enabled, support_multi_writer=support_multi_writer, support_disk_labels=support_disk_labels, ) instances_flags.AddCanIpForwardArgs(parser) instances_flags.AddContainerMountDiskFlag(parser) instances_flags.AddAddressArgs( parser, instances=True, containers=True, support_ipv6_only=support_ipv6_only, ) instances_flags.AddAcceleratorArgs(parser) instances_flags.AddMachineTypeArgs(parser) instances_flags.AddMaintenancePolicyArgs( parser, deprecate=deprecate_maintenance_policy ) instances_flags.AddNoRestartOnFailureArgs(parser) instances_flags.AddPreemptibleVmArgs(parser) instances_flags.AddProvisioningModelVmArgs( parser, support_flex_start=support_flex_start, ) instances_flags.AddInstanceTerminationActionVmArgs(parser) instances_flags.AddServiceAccountAndScopeArgs(parser, False) instances_flags.AddTagsArgs(parser) instances_flags.AddCustomMachineTypeArgs(parser) instances_flags.AddNetworkArgs(parser) instances_flags.AddPrivateNetworkIpArgs(parser) instances_flags.AddNetworkPerformanceConfigsArgs(parser) instances_flags.AddShieldedInstanceConfigArgs( parser=parser, for_container=True ) instances_flags.AddKonletArgs(parser) instances_flags.AddPublicPtrArgs(parser, instance=True) instances_flags.AddImageArgs(parser) instances_flags.AddConfidentialComputeArgs( parser, support_confidential_compute_type, support_confidential_compute_type_tdx, support_snp_svsm, ) instances_flags.AddNestedVirtualizationArgs(parser) instances_flags.AddThreadsPerCoreArgs(parser) instances_flags.AddIPv6AddressArgs(parser) instances_flags.AddIPv6PrefixLengthArgs(parser) labels_util.AddCreateLabelsFlags(parser) instances_flags.AddStackTypeArgs(parser, support_ipv6_only) instances_flags.AddIpv6NetworkTierArgs(parser) instances_flags.AddInternalIPv6AddressArgs(parser) instances_flags.AddInternalIPv6PrefixLengthArgs(parser) instances_flags.AddMaxRunDurationVmArgs(parser) instances_flags.AddDiscardLocalSsdVmArgs(parser) if support_graceful_shutdown: instances_flags.AddGracefulShutdownArgs(parser, is_create=True) instances_flags.AddReservationAffinityGroup( parser, group_text='Specifies the reservation for the instance.', affinity_text='The type of reservation for the instance.', support_specific_then_x_affinity=support_specific_then_x_affinity, ) maintenance_flags.AddResourcePoliciesArgs(parser, 'added to', 'instance') parser.add_argument( '--description', help='Specifies a textual description of the instances.' ) instances_flags.INSTANCES_ARG.AddArgument(parser, operation_type='create') CreateWithContainer.SOURCE_INSTANCE_TEMPLATE = ( instances_flags.MakeSourceInstanceTemplateArg() ) CreateWithContainer.SOURCE_INSTANCE_TEMPLATE.AddArgument(parser) parser.display_info.AddCacheUpdater(completers.InstancesCompleter) if support_skip_guest_os_shutdown: instances_flags.AddSkipGuestOsShutdownArgs(parser) if support_workload_identity_config: instances_flags.AddWorkloadIdentityConfigArgs(parser) instances_flags.AddRequestValidForDurationArgs(parser) @base.Deprecate( is_removed=False, warning=( 'The option to deploy a container during VM creation using the' ' container startup agent is deprecated. Use alternative services to' ' run containers on your VMs. Learn more at' ' https://cloud.google.com/compute/docs/containers/migrate-containers.' ), error=( 'The option to deploy a container during VM creation using the' ' container startup agent is deprecated. Use alternative services to' ' run containers on your VMs. Learn more at' ' https://cloud.google.com/compute/docs/containers/migrate-containers.' ), ) # TODO(b/305707759):Change @base.DefaultUniverseOnly to # @base.UniverseCompatible once b/305707759 is fixed. # See go/gcloud-cli-running-tpc-tests. @base.DefaultUniverseOnly @base.ReleaseTracks(base.ReleaseTrack.GA) class CreateWithContainer(base.CreateCommand): """Command for creating VM instances running container images.""" _container_mount_disk_enabled = True _support_create_boot_disk = True _support_match_container_mount_disks = True _support_nvdimm = False _support_host_error_timeout_seconds = True _support_numa_node_count = False _support_visible_core_count = True _support_confidential_compute_type = True _support_confidential_compute_type_tdx = True _support_snp_svsm = False _support_local_ssd_recovery_timeout = True _support_specific_then_x_affinity = False _support_disk_labels = False _support_max_run_duration = True _support_graceful_shutdown = True _support_skip_guest_os_shutdown = True _support_workload_identity_config = False @staticmethod def Args(parser): """Register parser args.""" _Args( parser, container_mount_enabled=True, support_multi_writer=False, support_confidential_compute_type=True, support_confidential_compute_type_tdx=True, support_snp_svsm=False, support_specific_then_x_affinity=False, support_disk_labels=False, support_ipv6_only=True, support_skip_guest_os_shutdown=True, support_workload_identity_config=False, ) instances_flags.AddNetworkTierArgs(parser, instance=True) instances_flags.AddMinCpuPlatformArgs(parser, base.ReleaseTrack.GA) instances_flags.AddPrivateIpv6GoogleAccessArg( parser, utils.COMPUTE_GA_API_VERSION ) instances_flags.AddVisibleCoreCountArgs(parser) instances_flags.AddLocalSsdRecoveryTimeoutArgs(parser) instances_flags.AddHostErrorTimeoutSecondsArgs(parser) def _ValidateArgs(self, args): self._ValidateTrackSpecificArgs(args) instances_flags.ValidateAcceleratorArgs(args) instances_flags.ValidateNicFlags(args) instances_flags.ValidateNetworkTierArgs(args) instances_flags.ValidateKonletArgs(args) instances_flags.ValidateDiskCommonFlags(args) instances_flags.ValidateServiceAccountAndScopeArgs(args) instances_flags.ValidatePublicPtrFlags(args) instances_flags.ValidateNetworkPerformanceConfigsArgs(args) instances_flags.ValidateInstanceScheduling( args, self._support_max_run_duration ) instances_flags.ValidateNetworkTierArgs(args) instances_flags.ValidateReservationAffinityGroup(args) if instance_utils.UseExistingBootDisk(args.disk or []): raise exceptions.InvalidArgumentException( '--disk', 'Boot disk specified for containerized VM.' ) def _ValidateTrackSpecificArgs(self, args): return None def GetImageUri(self, args, compute_client, resource_parser, instance_refs): if ( args.IsSpecified('image') or args.IsSpecified('image_family') or args.IsSpecified('image_project') ): image_expander = image_utils.ImageExpander( compute_client, resource_parser ) image_uri, _ = image_expander.ExpandImageFlag( user_project=instance_refs[0].project, image=args.image, image_family=args.image_family, image_project=args.image_project, ) if resource_parser.Parse(image_uri).project != 'cos-cloud': log.warning( 'This container deployment mechanism requires a ' 'Container-Optimized OS image in order to work. Select an ' 'image from a cos-cloud project (cos-stable, cos-beta, ' 'cos-dev image families).' ) else: image_uri = containers_utils.ExpandKonletCosImageFlag(compute_client) return image_uri def _GetNetworkInterfaces( self, args, client, holder, project, location, scope, skip_defaults ): return create_utils.GetNetworkInterfaces( args, client, holder, project, location, scope, skip_defaults, support_internal_ipv6_reservation=True, ) def GetNetworkInterfaces( self, args, resources, client, holder, project, location, scope, skip_defaults, ): if args.network_interface: return create_utils.CreateNetworkInterfaceMessages( resources=resources, compute_client=client, network_interface_arg=args.network_interface, project=project, location=location, scope=scope, support_internal_ipv6_reservation=True, ) return self._GetNetworkInterfaces( args, client, holder, project, location, scope, skip_defaults ) def CheckDiskMessageArgs(self, args, skip_defaults): """Creates API messages with disks attached to VM instance.""" flags_to_check = [ 'create_disk', 'local_ssd', 'boot_disk_type', 'boot_disk_device_name', 'boot_disk_auto_delete', 'boot_disk_provisioned_iops', ] if hasattr(args, 'local_nvdimm'): flags_to_check.append('local_nvdimm') if ( skip_defaults and not args.IsSpecified('disk') and not instance_utils.IsAnySpecified(args, *flags_to_check) ): return False return True def Run(self, args): self._ValidateArgs(args) holder = base_classes.ComputeApiHolder(self.ReleaseTrack()) compute_client = holder.client resource_parser = holder.resources container_mount_disk = instances_flags.GetValidatedContainerMountDisk( holder, args.container_mount_disk, args.disk, args.create_disk ) source_instance_template = instance_utils.GetSourceInstanceTemplate( args, resource_parser, self.SOURCE_INSTANCE_TEMPLATE ) skip_defaults = instance_utils.GetSkipDefaults(source_instance_template) scheduling = instance_utils.GetScheduling( args, compute_client, skip_defaults, support_min_node_cpu=False, support_host_error_timeout_seconds=self._support_host_error_timeout_seconds, support_max_run_duration=self._support_max_run_duration, support_local_ssd_recovery_timeout=self._support_local_ssd_recovery_timeout, support_graceful_shutdown=self._support_graceful_shutdown, support_skip_guest_os_shutdown=self._support_skip_guest_os_shutdown, ) service_accounts = instance_utils.GetServiceAccounts( args, compute_client, skip_defaults ) user_metadata = instance_utils.GetValidatedMetadata(args, compute_client) boot_disk_size_gb = instance_utils.GetBootDiskSizeGb(args) instance_refs = instance_utils.GetInstanceRefs(args, compute_client, holder) network_interfaces = self.GetNetworkInterfaces( args, resource_parser, compute_client, holder, instance_refs[0].project, instance_refs[0].zone, compute_scopes.ScopeEnum.ZONE, skip_defaults, ) image_uri = self.GetImageUri( args, compute_client, resource_parser, instance_refs ) labels = containers_utils.GetLabelsMessageWithCosVersion( args.labels, image_uri, resource_parser, compute_client.messages.Instance, ) can_ip_forward = instance_utils.GetCanIpForward(args, skip_defaults) tags = containers_utils.CreateTagsMessage( compute_client.messages, args.tags ) confidential_vm_type = instance_utils.GetConfidentialVmType( args, self._support_confidential_compute_type ) workload_identity_config = ( instance_utils.CreateWorkloadIdentityConfigMessage( args, compute_client.messages, self._support_workload_identity_config, ) ) requests = [] for instance_ref in instance_refs: metadata = containers_utils.CreateKonletMetadataMessage( compute_client.messages, args, instance_ref.Name(), user_metadata, container_mount_disk_enabled=self._container_mount_disk_enabled, container_mount_disk=container_mount_disk, ) disks = [] if self.CheckDiskMessageArgs(args, skip_defaults): disks = create_utils.CreateDiskMessages( args=args, instance_name=instance_ref.Name(), project=instance_ref.project, location=instance_ref.zone, scope=compute_scopes.ScopeEnum.ZONE, compute_client=compute_client, resource_parser=resource_parser, boot_disk_size_gb=boot_disk_size_gb, image_uri=image_uri, create_boot_disk=self._support_create_boot_disk, support_nvdimm=self._support_nvdimm, support_match_container_mount_disks=self._support_match_container_mount_disks, support_disk_labels=self._support_disk_labels, ) machine_type_uri = None if instance_utils.CheckSpecifiedMachineTypeArgs(args, skip_defaults): machine_type_uri = create_utils.CreateMachineTypeUri( args=args, compute_client=compute_client, resource_parser=resource_parser, project=instance_ref.project, location=instance_ref.zone, scope=compute_scopes.ScopeEnum.ZONE, confidential_vm_type=confidential_vm_type, ) guest_accelerators = create_utils.GetAccelerators( args=args, compute_client=compute_client, resource_parser=resource_parser, project=instance_ref.project, location=instance_ref.zone, scope=compute_scopes.ScopeEnum.ZONE, ) instance = compute_client.messages.Instance( canIpForward=can_ip_forward, disks=disks, guestAccelerators=guest_accelerators, description=args.description, labels=labels, machineType=machine_type_uri, metadata=metadata, minCpuPlatform=args.min_cpu_platform, name=instance_ref.Name(), networkInterfaces=network_interfaces, serviceAccounts=service_accounts, scheduling=scheduling, tags=tags, ) if self._support_workload_identity_config and workload_identity_config: instance.workloadIdentityConfig = workload_identity_config if args.private_ipv6_google_access_type is not None: instance.privateIpv6GoogleAccess = ( instances_flags.GetPrivateIpv6GoogleAccessTypeFlagMapper( compute_client.messages ).GetEnumForChoice(args.private_ipv6_google_access_type) ) if args.IsKnownAndSpecified('request_valid_for_duration'): instance.params = instance_utils.CreateParams(args, compute_client) confidential_instance_config = create_utils.BuildConfidentialInstanceConfigMessage( messages=compute_client.messages, args=args, support_confidential_compute_type=self._support_confidential_compute_type, support_confidential_compute_type_tdx=self._support_confidential_compute_type_tdx, support_snp_svsm=self._support_snp_svsm, ) if confidential_instance_config: instance.confidentialInstanceConfig = confidential_instance_config has_visible_core_count = ( self._support_visible_core_count and args.visible_core_count is not None ) if ( args.enable_nested_virtualization is not None or args.threads_per_core is not None or ( self._support_numa_node_count and args.numa_node_count is not None ) or has_visible_core_count ): visible_core_count = ( args.visible_core_count if has_visible_core_count else None ) instance.advancedMachineFeatures = ( instance_utils.CreateAdvancedMachineFeaturesMessage( compute_client.messages, args.enable_nested_virtualization, args.threads_per_core, args.numa_node_count if self._support_numa_node_count else None, visible_core_count, ) ) resource_policies = getattr(args, 'resource_policies', None) if resource_policies: parsed_resource_policies = [] for policy in resource_policies: resource_policy_ref = maintenance_util.ParseResourcePolicyWithZone( resource_parser, policy, project=instance_ref.project, zone=instance_ref.zone, ) parsed_resource_policies.append(resource_policy_ref.SelfLink()) instance.resourcePolicies = parsed_resource_policies shielded_instance_config = ( create_utils.BuildShieldedInstanceConfigMessage( messages=compute_client.messages, args=args ) ) if shielded_instance_config: instance.shieldedInstanceConfig = shielded_instance_config if args.IsSpecified('network_performance_configs'): instance.networkPerformanceConfig = ( instance_utils.GetNetworkPerformanceConfig(args, compute_client) ) request = compute_client.messages.ComputeInstancesInsertRequest( instance=instance, sourceInstanceTemplate=source_instance_template, project=instance_ref.project, zone=instance_ref.zone, ) request.instance.reservationAffinity = ( instance_utils.GetReservationAffinity( args, compute_client, self._support_specific_then_x_affinity ) ) requests.append( (compute_client.apitools_client.instances, 'Insert', request) ) return compute_client.MakeRequests(requests) @base.ReleaseTracks(base.ReleaseTrack.BETA) class CreateWithContainerBeta(CreateWithContainer): """Command for creating VM instances running container images.""" _container_mount_disk_enabled = True _support_create_boot_disk = True _support_match_container_mount_disks = True _support_nvdimm = False _support_visible_core_count = True _support_confidential_compute_type = True _support_confidential_compute_type_tdx = True _support_snp_svsm = False _support_host_error_timeout_seconds = True _support_numa_node_count = False _support_local_ssd_recovery_timeout = True _support_specific_then_x_affinity = True _support_disk_labels = True _support_skip_guest_os_shutdown = True _support_workload_identity_config = False @staticmethod def Args(parser): """Register parser args.""" _Args( parser, container_mount_enabled=True, support_confidential_compute_type=True, support_confidential_compute_type_tdx=True, support_snp_svsm=False, support_specific_then_x_affinity=True, support_disk_labels=True, support_graceful_shutdown=True, support_ipv6_only=True, support_flex_start=True, support_skip_guest_os_shutdown=True, support_workload_identity_config=False, ) instances_flags.AddNetworkTierArgs(parser, instance=True) instances_flags.AddLocalSsdArgs(parser) instances_flags.AddMinCpuPlatformArgs(parser, base.ReleaseTrack.BETA) instances_flags.AddPrivateIpv6GoogleAccessArg( parser, utils.COMPUTE_BETA_API_VERSION ) instances_flags.AddHostErrorTimeoutSecondsArgs(parser) instances_flags.AddVisibleCoreCountArgs(parser) instances_flags.AddLocalSsdRecoveryTimeoutArgs(parser) def _ValidateTrackSpecificArgs(self, args): instances_flags.ValidateLocalSsdFlags(args) @base.ReleaseTracks(base.ReleaseTrack.ALPHA) class CreateWithContainerAlpha(CreateWithContainerBeta): """Alpha version of compute instances create-with-container command.""" _container_mount_disk_enabled = True _support_create_boot_disk = True _support_match_container_mount_disks = True _support_nvdimm = True _support_host_error_timeout_seconds = True _support_numa_node_count = True _support_visible_core_count = True _support_confidential_compute_type = True _support_confidential_compute_type_tdx = True _support_snp_svsm = True _support_local_ssd_recovery_timeout = True _support_specific_then_x_affinity = True _support_disk_labels = True _support_ipv6_only = True _support_skip_guest_os_shutdown = True _support_workload_identity_config = True @staticmethod def Args(parser): _Args( parser, deprecate_maintenance_policy=True, container_mount_enabled=True, support_confidential_compute_type=True, support_confidential_compute_type_tdx=True, support_snp_svsm=True, support_specific_then_x_affinity=True, support_disk_labels=True, support_ipv6_only=True, support_graceful_shutdown=True, support_flex_start=True, support_skip_guest_os_shutdown=True, support_workload_identity_config=True, ) instances_flags.AddNetworkTierArgs(parser, instance=True) instances_flags.AddLocalSsdArgsWithSize(parser) instances_flags.AddLocalNvdimmArgs(parser) instances_flags.AddMinCpuPlatformArgs(parser, base.ReleaseTrack.ALPHA) instances_flags.AddPublicDnsArgs(parser, instance=True) instances_flags.AddPrivateIpv6GoogleAccessArg( parser, utils.COMPUTE_ALPHA_API_VERSION ) instances_flags.AddHostErrorTimeoutSecondsArgs(parser) instances_flags.AddLocalSsdRecoveryTimeoutArgs(parser) instances_flags.AddNumaNodeCountArgs(parser) instances_flags.AddVisibleCoreCountArgs(parser) instances_flags.AddIPv6AddressAlphaArgs(parser) instances_flags.AddIPv6PrefixLengthAlphaArgs(parser) def _ValidateTrackSpecificArgs(self, args): instances_flags.ValidateLocalSsdFlags(args) instances_flags.ValidatePublicDnsFlags(args) instances_flags.ValidatePublicPtrFlags(args) def _GetNetworkInterfaces( self, args, client, holder, project, location, scope, skip_defaults ): return create_utils.GetNetworkInterfacesAlpha( args, client, holder, project, location, scope, skip_defaults ) CreateWithContainer.detailed_help = { 'brief': """\ Creates Compute Engine virtual machine instances running container images. """, 'DESCRIPTION': """\ *{command}* creates Compute Engine virtual machines that runs a Docker image. For example: $ {command} instance-1 --zone us-central1-a \ --container-image=gcr.io/google-containers/busybox creates an instance called instance-1, in the us-central1-a zone, running the 'busybox' image. For more examples, refer to the *EXAMPLES* section below. """, 'EXAMPLES': """\ To run the gcr.io/google-containers/busybox image on an instance named 'instance-1' that executes 'echo "Hello world"' as a run command, run: $ {command} instance-1 \ --container-image=gcr.io/google-containers/busybox \ --container-command='echo "Hello world"' To run the gcr.io/google-containers/busybox image in privileged mode, run: $ {command} instance-1 \ --container-image=gcr.io/google-containers/busybox --container-privileged """, }