274 lines
11 KiB
Python
274 lines
11 KiB
Python
# -*- coding: utf-8 -*- #
|
|
# Copyright 2019 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 importing instances in OVF format into GCE."""
|
|
|
|
from __future__ import absolute_import
|
|
from __future__ import division
|
|
from __future__ import unicode_literals
|
|
|
|
import re
|
|
|
|
from googlecloudsdk.api_lib.compute import base_classes
|
|
from googlecloudsdk.api_lib.compute import daisy_utils
|
|
from googlecloudsdk.api_lib.compute import image_utils
|
|
from googlecloudsdk.api_lib.compute import instance_utils
|
|
from googlecloudsdk.calliope import base
|
|
from googlecloudsdk.calliope import exceptions
|
|
from googlecloudsdk.command_lib.compute import completers
|
|
from googlecloudsdk.command_lib.compute.images import os_choices
|
|
from googlecloudsdk.command_lib.compute.instances import flags as instances_flags
|
|
from googlecloudsdk.command_lib.compute.sole_tenancy import flags as sole_tenancy_flags
|
|
from googlecloudsdk.command_lib.util.args import labels_util
|
|
from googlecloudsdk.core import log
|
|
from googlecloudsdk.core import properties
|
|
from googlecloudsdk.core import resources
|
|
|
|
_OUTPUT_FILTER = ['[Daisy', '[import-', 'starting build', ' import', 'ERROR']
|
|
|
|
|
|
@base.DefaultUniverseOnly
|
|
@base.ReleaseTracks(base.ReleaseTrack.GA)
|
|
class Import(base.CreateCommand):
|
|
"""Import an instance into Compute Engine from OVF."""
|
|
|
|
_OS_CHOICES = os_choices.OS_CHOICES_INSTANCE_IMPORT_GA
|
|
|
|
@classmethod
|
|
def Args(cls, parser):
|
|
compute_holder = cls._GetComputeApiHolder(no_http=True)
|
|
messages = compute_holder.client.messages
|
|
|
|
instances_flags.AddCanIpForwardArgs(parser)
|
|
instances_flags.AddMachineTypeArgs(parser)
|
|
instances_flags.AddNoRestartOnFailureArgs(parser)
|
|
instances_flags.AddTagsArgs(parser)
|
|
instances_flags.AddCustomMachineTypeArgs(parser)
|
|
instances_flags.AddNetworkArgs(parser)
|
|
instances_flags.AddPrivateNetworkIpArgs(parser)
|
|
instances_flags.AddDeletionProtectionFlag(parser)
|
|
instances_flags.AddNetworkTierArgs(parser, instance=True)
|
|
instances_flags.AddNoAddressArg(parser)
|
|
labels_util.AddCreateLabelsFlags(parser)
|
|
daisy_utils.AddCommonDaisyArgs(parser, operation='an import')
|
|
daisy_utils.AddExtraCommonDaisyArgs(parser)
|
|
instances_flags.INSTANCES_ARG_FOR_IMPORT.AddArgument(
|
|
parser, operation_type='import')
|
|
daisy_utils.AddOVFSourceUriArg(parser)
|
|
parser.add_argument(
|
|
'--os',
|
|
required=False,
|
|
choices=sorted(cls._OS_CHOICES),
|
|
help='Specifies the OS of the image being imported.')
|
|
daisy_utils.AddByolArg(parser)
|
|
|
|
image_utils.AddGuestOsFeaturesArgForImport(parser, messages)
|
|
|
|
parser.add_argument(
|
|
'--description',
|
|
help='Specifies a textual description of the VM instances.')
|
|
daisy_utils.AddGuestEnvironmentArg(parser)
|
|
parser.display_info.AddCacheUpdater(completers.InstancesCompleter)
|
|
sole_tenancy_flags.AddNodeAffinityFlagToParser(parser)
|
|
parser.add_argument(
|
|
'--hostname',
|
|
help="""\
|
|
Specify the hostname of the VM instance to be imported. The specified
|
|
hostname must be RFC1035 compliant. If hostname is not specified, the
|
|
default hostname is [INSTANCE_NAME].c.[PROJECT_ID].internal when using
|
|
the global DNS, and [INSTANCE_NAME].[ZONE].c.[PROJECT_ID].internal
|
|
when using zonal DNS.
|
|
""")
|
|
daisy_utils.AddComputeServiceAccountArg(
|
|
parser, 'instance import',
|
|
daisy_utils.IMPORT_ROLES_FOR_COMPUTE_SERVICE_ACCOUNT)
|
|
instances_flags.AddServiceAccountAndScopeArgs(
|
|
parser,
|
|
False,
|
|
extra_scopes_help=(
|
|
'However, if neither `--scopes` nor `--no-scopes` are '
|
|
'specified and the project has no default service '
|
|
'account, then the VM instance is imported with no '
|
|
'scopes. Note that the level of access that a service '
|
|
'account has is determined by a combination of access '
|
|
'scopes and IAM roles so you must configure both '
|
|
'access scopes and IAM roles for the service account '
|
|
'to work properly.'),
|
|
operation='Import')
|
|
|
|
daisy_utils.AddCloudBuildServiceAccountArg(
|
|
parser,
|
|
'instance import',
|
|
daisy_utils.IMPORT_ROLES_FOR_CLOUDBUILD_SERVICE_ACCOUNT,
|
|
)
|
|
|
|
@classmethod
|
|
def _GetComputeApiHolder(cls, no_http=False):
|
|
return base_classes.ComputeApiHolder(cls.ReleaseTrack(), no_http)
|
|
|
|
def _ValidateInstanceName(self, args):
|
|
"""Raise an exception if requested instance name is invalid."""
|
|
instance_name_pattern = re.compile('^[a-z]([-a-z0-9]{0,61}[a-z0-9])?$')
|
|
if not instance_name_pattern.match(args.instance_name):
|
|
raise exceptions.InvalidArgumentException(
|
|
'INSTANCE_NAME',
|
|
'Name must start with a lowercase letter followed by up to '
|
|
'63 lowercase letters, numbers, or hyphens, and cannot end '
|
|
'with a hyphen.')
|
|
|
|
def _CheckForExistingInstances(self, instance_name, client):
|
|
"""Check that the destination instances do not already exist."""
|
|
|
|
zone = properties.VALUES.compute.zone.GetOrFail()
|
|
request = (client.apitools_client.instances, 'Get',
|
|
client.messages.ComputeInstancesGetRequest(
|
|
instance=instance_name,
|
|
project=properties.VALUES.core.project.GetOrFail(),
|
|
zone=zone))
|
|
errors = []
|
|
instances = client.MakeRequests([request], errors_to_collect=errors)
|
|
if not errors and instances:
|
|
message = ('The VM instance [{instance_name}] already exists in zone '
|
|
'[{zone}].').format(
|
|
instance_name=instance_name, zone=zone)
|
|
raise exceptions.InvalidArgumentException('INSTANCE_NAME', message)
|
|
|
|
def _ValidateArgs(self, args, compute_client):
|
|
self._ValidateInstanceName(args)
|
|
self._CheckForExistingInstances(args.instance_name, compute_client)
|
|
|
|
instances_flags.ValidateNicFlags(args)
|
|
instances_flags.ValidateNetworkTierArgs(args)
|
|
daisy_utils.ValidateZone(args, compute_client)
|
|
instances_flags.ValidateServiceAccountAndScopeArgs(args)
|
|
|
|
def Run(self, args):
|
|
compute_holder = self._GetComputeApiHolder()
|
|
compute_client = compute_holder.client
|
|
messages = compute_client.messages
|
|
|
|
self._ValidateArgs(args, compute_client)
|
|
|
|
log.warning('Importing OVF. This may take 40 minutes for smaller OVFs '
|
|
'and up to a couple of hours for larger OVFs.')
|
|
|
|
machine_type = None
|
|
if args.machine_type or args.custom_cpu or args.custom_memory:
|
|
machine_type = instance_utils.InterpretMachineType(
|
|
machine_type=args.machine_type,
|
|
custom_cpu=args.custom_cpu,
|
|
custom_memory=args.custom_memory,
|
|
ext=getattr(args, 'custom_extensions', None),
|
|
vm_type=getattr(args, 'custom_vm_type', None))
|
|
|
|
try:
|
|
source_uri = daisy_utils.MakeGcsUri(args.source_uri)
|
|
except resources.UnknownCollectionException:
|
|
raise exceptions.InvalidArgumentException(
|
|
'source-uri',
|
|
'must be a path to an object or a directory in Cloud Storage')
|
|
|
|
# The value of the attribute 'guest_os_features' can be can be a list, None,
|
|
# or the attribute may not be present at all.
|
|
# We treat the case when it is None or when it is not present as if the list
|
|
# of features is empty. We need to use the trailing `or ()` rather than
|
|
# give () as a default value to getattr() to handle the case where
|
|
# args.guest_os_features is present, but it is None.
|
|
guest_os_features = getattr(args, 'guest_os_features', None) or ()
|
|
uefi_compatible = (
|
|
messages.GuestOsFeature.TypeValueValuesEnum.UEFI_COMPATIBLE.name
|
|
in guest_os_features)
|
|
|
|
return daisy_utils.RunInstanceOVFImportBuild(
|
|
args=args,
|
|
compute_client=compute_client,
|
|
instance_name=args.instance_name,
|
|
source_uri=source_uri,
|
|
no_guest_environment=not args.guest_environment,
|
|
can_ip_forward=args.can_ip_forward,
|
|
deletion_protection=args.deletion_protection,
|
|
description=args.description,
|
|
labels=args.labels,
|
|
machine_type=machine_type,
|
|
network=args.network,
|
|
network_tier=args.network_tier,
|
|
subnet=args.subnet,
|
|
private_network_ip=args.private_network_ip,
|
|
no_restart_on_failure=not args.restart_on_failure,
|
|
os=args.os,
|
|
byol=getattr(args, 'byol', False),
|
|
uefi_compatible=uefi_compatible,
|
|
tags=args.tags,
|
|
zone=properties.VALUES.compute.zone.Get(),
|
|
project=args.project,
|
|
output_filter=_OUTPUT_FILTER,
|
|
release_track=(
|
|
self.ReleaseTrack().id.lower() if self.ReleaseTrack() else None
|
|
),
|
|
hostname=getattr(args, 'hostname', None),
|
|
no_address=getattr(args, 'no_address', False),
|
|
compute_service_account=getattr(args, 'compute_service_account', ''),
|
|
cloudbuild_service_account=getattr(
|
|
args, 'cloudbuild_service_account', ''
|
|
),
|
|
scopes=getattr(args, 'scopes', None),
|
|
no_scopes=getattr(args, 'no_scopes', False),
|
|
service_account=getattr(args, 'service_account', None),
|
|
no_service_account=getattr(args, 'no_service_account', False),
|
|
)
|
|
|
|
|
|
@base.ReleaseTracks(base.ReleaseTrack.BETA, base.ReleaseTrack.ALPHA)
|
|
class ImportBeta(Import):
|
|
"""Import an instance into Compute Engine from OVF."""
|
|
|
|
_OS_CHOICES = os_choices.OS_CHOICES_INSTANCE_IMPORT_BETA
|
|
|
|
@classmethod
|
|
def Args(cls, parser):
|
|
super(ImportBeta, cls).Args(parser)
|
|
|
|
# pylint: disable=useless-super-delegation
|
|
def _ValidateArgs(self, args, compute_client):
|
|
super(ImportBeta, self)._ValidateArgs(args, compute_client)
|
|
|
|
|
|
Import.detailed_help = {
|
|
'brief': (
|
|
'Create Compute Engine virtual machine instances from virtual '
|
|
'appliance in OVA/OVF format.'),
|
|
'DESCRIPTION':
|
|
"""\
|
|
*{command}* creates Compute Engine virtual machine instances from
|
|
virtual appliance in OVA/OVF format.
|
|
|
|
Importing OVF involves:
|
|
* Unpacking OVF package (if in OVA format) to Cloud Storage.
|
|
* Import disks from OVF to Compute Engine.
|
|
* Translate the boot disk to make it bootable in Compute Engine.
|
|
* Create a VM instance using OVF metadata and imported disks and boot it.
|
|
|
|
OVF import tool requires Cloud Build to be enabled. See [](https://cloud.google.com/compute/docs/import/import-ovf-files#enable-cloud-build)
|
|
Virtual machine instances, images and disks in Compute engine and files
|
|
stored on Cloud Storage incur charges. See [](https://cloud.google.com/compute/docs/images/importing-virtual-disks#resource_cleanup).
|
|
""",
|
|
'EXAMPLES':
|
|
"""\
|
|
To import an OVF package from Cloud Storage into a VM named
|
|
`my-instance`, run:
|
|
|
|
$ {command} my-instance --source-uri=gs://my-bucket/my-dir
|
|
""",
|
|
}
|