feat: Add new gcloud commands, API clients, and third-party libraries across various services.

This commit is contained in:
2026-01-01 20:26:35 +01:00
parent 5e23cbece0
commit a19e592eb7
25221 changed files with 8324611 additions and 0 deletions

View File

@@ -0,0 +1,42 @@
# -*- coding: utf-8 -*- #
# Copyright 2014 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.
"""Commands for reading and manipulating images."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
@base.UniverseCompatible
@base.ReleaseTracks(
base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA, base.ReleaseTrack.GA)
class Images(base.Group):
"""List, create, and delete Compute Engine images."""
Images.category = base.INSTANCES_CATEGORY
Images.detailed_help = {
'DESCRIPTION': """
Create and manipulate Compute Engine images.
For more information about images, see the
[images documentation](https://cloud.google.com/compute/docs/images).
See also: [Images API](https://cloud.google.com/compute/docs/reference/rest/v1/images).
""",
}

View File

@@ -0,0 +1,43 @@
release_tracks: [ALPHA, BETA, GA]
help_text:
brief: Add IAM policy binding to a Compute Engine image.
description: |
Add an IAM policy binding to the IAM policy of a Compute Engine image. One binding consists of a member,
a role, and an optional condition.
examples: |
To add an IAM policy binding for the role of 'roles/compute.securityAdmin' for the user 'test-user@gmail.com'
with image 'my-image', run:
$ {command} my-image --member='user:test-user@gmail.com' --role='roles/editor'
To add an IAM policy binding which expires at the end of the year 2018 for the role of
'roles/compute.securityAdmin' and the user 'test-user@gmail.com' with image 'my-image', run:
$ {command} my-image --member='user:test-user@gmail.com' --role='roles/compute.securityAdmin' --condition='expression=request.time < timestamp("2019-01-01T00:00:00Z"),title=expires_end_of_2018,description=Expires at midnight on 2018-12-31'
See https://cloud.google.com/iam/docs/managing-policies for details of
policy role and member types.
request:
collection: compute.images
use_relative_name: false
api_version: v1
BETA:
api_version: beta
ALPHA:
api_version: alpha
arguments:
resource:
help_text: The image for which to add IAM policy binding to.
spec: !REF googlecloudsdk.command_lib.compute.resources:image
iam:
set_iam_policy_request_path: globalSetPolicyRequest
message_type_overrides:
policy: Policy
set_iam_policy_request: ComputeImagesSetIamPolicyRequest
enable_condition: true
policy_version: 3
get_iam_policy_version_path: optionsRequestedPolicyVersion

View File

@@ -0,0 +1,84 @@
# -*- 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 adding labels to 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.operations import poller
from googlecloudsdk.api_lib.util import waiter
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.compute import flags
from googlecloudsdk.command_lib.compute import labels_doc_helper
from googlecloudsdk.command_lib.compute import labels_flags
from googlecloudsdk.command_lib.compute.images import flags as images_flags
from googlecloudsdk.command_lib.util.args import labels_util
@base.ReleaseTracks(
base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA, base.ReleaseTrack.GA)
class ImagesAddLabels(base.UpdateCommand):
DISK_IMAGE_ARG = None
@classmethod
def Args(cls, parser):
cls.DISK_IMAGE_ARG = images_flags.MakeDiskImageArg(plural=False)
cls.DISK_IMAGE_ARG.AddArgument(parser)
labels_flags.AddArgsForAddLabels(parser)
def Run(self, args):
holder = base_classes.ComputeApiHolder(self.ReleaseTrack())
client = holder.client.apitools_client
messages = holder.client.messages
image_ref = self.DISK_IMAGE_ARG.ResolveAsResource(
args, holder.resources,
scope_lister=flags.GetDefaultScopeLister(holder.client))
add_labels = labels_util.GetUpdateLabelsDictFromArgs(args)
image = client.images.Get(
messages.ComputeImagesGetRequest(**image_ref.AsDict()))
labels_update = labels_util.Diff(additions=add_labels).Apply(
messages.GlobalSetLabelsRequest.LabelsValue, image.labels)
if not labels_update.needs_update:
return image
request = messages.ComputeImagesSetLabelsRequest(
project=image_ref.project,
resource=image_ref.image,
globalSetLabelsRequest=
messages.GlobalSetLabelsRequest(
labelFingerprint=image.labelFingerprint,
labels=labels_update.labels))
operation = client.images.SetLabels(request)
operation_ref = holder.resources.Parse(
operation.selfLink, collection='compute.globalOperations')
operation_poller = poller.Poller(client.images)
return waiter.WaitFor(
operation_poller, operation_ref,
'Updating labels of image [{0}]'.format(
image_ref.Name()))
ImagesAddLabels.detailed_help = (
labels_doc_helper.GenerateDetailedHelpForAddLabels('image'))

View File

@@ -0,0 +1,444 @@
# -*- coding: utf-8 -*- #
# Copyright 2014 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 images."""
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 csek_utils
from googlecloudsdk.api_lib.compute import image_utils
from googlecloudsdk.api_lib.compute import kms_utils
from googlecloudsdk.calliope import arg_parsers
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.command_lib.compute import flags as compute_flags
from googlecloudsdk.command_lib.compute import resource_manager_tags_utils
from googlecloudsdk.command_lib.compute.images import flags
from googlecloudsdk.command_lib.kms import resource_args as kms_resource_args
from googlecloudsdk.command_lib.util.args import labels_util
from googlecloudsdk.core import resources
import six
POLL_TIMEOUT = 36000 # 10 hours is recommended by PD team b/131850402#comment20
def _Args(
parser,
messages,
supports_force_create=False,
support_user_licenses=False,
supports_rollout_override=False,
support_tags=False,
):
"""Set Args based on Release Track."""
# GA Args
parser.display_info.AddFormat(flags.LIST_FORMAT)
sources_group = parser.add_mutually_exclusive_group(required=True)
flags.AddCommonArgs(parser, support_user_licenses=support_user_licenses)
flags.AddCommonSourcesArgs(parser, sources_group)
Create.DISK_IMAGE_ARG = flags.MakeDiskImageArg()
Create.DISK_IMAGE_ARG.AddArgument(parser, operation_type='create')
csek_utils.AddCsekKeyArgs(parser, resource_type='image')
labels_util.AddCreateLabelsFlags(parser)
flags.MakeForceArg().AddToParser(parser)
flags.AddCloningImagesArgs(parser, sources_group)
flags.AddCreatingImageFromSnapshotArgs(parser, sources_group)
image_utils.AddGuestOsFeaturesArg(parser, messages)
image_utils.AddArchitectureArg(parser, messages)
kms_resource_args.AddKmsKeyResourceArg(parser, 'image')
flags.AddSourceDiskProjectFlag(parser)
if support_tags:
parser.add_argument(
'--resource-manager-tags',
type=arg_parsers.ArgDict(),
metavar='KEY=VALUE',
help=(
'A comma-separated list of Resource Manager tags to apply to the'
' image.'
),
)
# Alpha and Beta Args
if supports_force_create:
# Deprecated as of Aug 2017.
flags.MakeForceCreateArg().AddToParser(parser)
parser.add_argument(
'--storage-location',
metavar='LOCATION',
help="""\
Specifies a Cloud Storage location, either regional or multi-regional,
where image content is to be stored. If not specified, the multi-region
location closest to the source is chosen automatically.
""")
parser.add_argument(
'--locked',
action='store_true',
default=None,
hidden=True,
help="""\
Specifies that any boot disk created from this image can't be used
for data backup operations such as snapshot creation, image creation,
instance snapshot creation, and disk cloning.
If a VM instance is created using this image, the boot disk is fixed
to this VM. The disk can't be attached to any other VMs, whether in
`read-write` mode or in `read-only` mode. Also, any VM created from this
disk, has the following characteristics:
* The VM can't be used for creating machine images or instance templates
* After the VM is created, you can't attach any secondary disk
* After the VM is deleted, the attached boot disk can't be retained
""")
# Alpha args
spec = {
'default_rollout_time': str,
'location_rollout_policies': arg_parsers.ArgDict(),
}
if supports_rollout_override:
parser.add_argument(
'--rollout-override',
type=arg_parsers.ArgDict(spec=spec),
help="""\
A rollout policy for the image. A rollout policy is used to restrict
the zones where this image is accessible when using a zonal image
family reference. When specified, the rollout policy overrides per-zone
references to the image through the associated image family. When the
rollout policy does not include the user specified zone, or if the zone
is rolled out, this image is accessible.
default_rollout_time
This is an optional RFC3339 timestamp on or after which
the update is considered rolled out to any zone that is not
explicitly stated.
location_rollout_policies
Location based rollout policies to apply to the resource.
Currently only zone names are supported as the key and must be
represented as valid URLs, like: zones/us-central1-a.
The value expects an RFC3339 timestamp on or after which the update
is considered rolled out to the specified location.
""",
)
compute_flags.AddShieldedInstanceInitialStateKeyArg(parser)
def _CreateImageParams(messages, resource_manager_tags):
"""Creates a disk params object for resource manager tags."""
resource_manager_tags_map = (
resource_manager_tags_utils.GetResourceManagerTags(resource_manager_tags)
)
params = messages.ImageParams
additional_properties = [
params.ResourceManagerTagsValue.AdditionalProperty(key=key, value=value)
for key, value in sorted(resource_manager_tags_map.items())
]
return params(
resourceManagerTags=params.ResourceManagerTagsValue(
additionalProperties=additional_properties
)
)
@base.DefaultUniverseOnly
@base.ReleaseTracks(base.ReleaseTrack.GA)
class Create(base.CreateCommand):
"""Create Compute Engine images."""
@classmethod
def Args(cls, parser):
messages = cls._GetApiHolder(no_http=True).client.messages
_Args(parser, messages, support_tags=False)
parser.display_info.AddCacheUpdater(flags.ImagesCompleter)
@classmethod
def _GetApiHolder(cls, no_http=False):
return base_classes.ComputeApiHolder(cls.ReleaseTrack(), no_http)
def Run(self, args):
return self._Run(args, support_tags=False)
def _Run(
self,
args,
support_user_licenses=False,
supports_rollout_override=False,
support_tags=False,
):
"""Returns a list of requests necessary for adding images."""
holder = self._GetApiHolder()
client = holder.client
messages = client.messages
resource_parser = holder.resources
image_ref = Create.DISK_IMAGE_ARG.ResolveAsResource(args, holder.resources)
image = messages.Image(
name=image_ref.image,
description=args.description,
sourceType=messages.Image.SourceTypeValueValuesEnum.RAW,
family=args.family)
if args.IsSpecified('architecture'):
image.architecture = messages.Image.ArchitectureValueValuesEnum(
args.architecture)
if support_user_licenses and args.IsSpecified('user_licenses'):
image.userLicenses = args.user_licenses
if support_tags and args.IsSpecified('resource_manager_tags'):
image.params = _CreateImageParams(messages, args.resource_manager_tags)
csek_keys = csek_utils.CsekKeyStore.FromArgs(args, True)
if csek_keys:
image.imageEncryptionKey = csek_utils.MaybeToMessage(
csek_keys.LookupKey(image_ref,
raise_if_missing=args.require_csek_key_create),
client.apitools_client)
image.imageEncryptionKey = kms_utils.MaybeGetKmsKey(
args, messages, image.imageEncryptionKey)
if supports_rollout_override and args.IsSpecified('rollout_override'):
location_rollout_policies = None
if 'location_rollout_policies' in args.rollout_override:
location_rollout_policies = messages.RolloutPolicy.LocationRolloutPoliciesValue(
additionalProperties=[
messages.RolloutPolicy.LocationRolloutPoliciesValue.AdditionalProperty(
key=k, value=v
)
for k, v in args.rollout_override[
'location_rollout_policies'
].items()
]
)
default_rollout_time = args.rollout_override.get('default_rollout_time')
image.rolloutOverride = messages.RolloutPolicy(
locationRolloutPolicies=location_rollout_policies,
defaultRolloutTime=default_rollout_time,
)
# Validate parameters.
if args.source_disk_zone and not args.source_disk:
raise exceptions.InvalidArgumentException(
'--source-disk-zone',
'You cannot specify [--source-disk-zone] unless you are specifying '
'[--source-disk].')
if args.source_disk_project and not args.source_disk:
raise exceptions.InvalidArgumentException(
'source_disk_project',
'You cannot specify [source_disk_project] unless you are '
'specifying [--source_disk].')
source_image_project = args.source_image_project
source_image = args.source_image
source_image_family = args.source_image_family
if source_image_project and not (source_image or source_image_family):
raise exceptions.InvalidArgumentException(
'--source-image-project',
'You cannot specify [--source-image-project] unless you are '
'specifying [--source-image] or [--source-image-family].')
if source_image or source_image_family:
image_expander = image_utils.ImageExpander(client, resource_parser)
_, source_image_ref = image_expander.ExpandImageFlag(
user_project=image_ref.project,
image=source_image,
image_family=source_image_family,
image_project=source_image_project,
return_image_resource=True)
image.sourceImage = source_image_ref.selfLink
image.sourceImageEncryptionKey = csek_utils.MaybeLookupKeyMessage(
csek_keys, source_image_ref, client.apitools_client)
if args.source_uri:
# For AR URIs, resources.REGISTRY.Parse will fail. In this case,
# we pass the URI to the API as is.
# The two formats for artifact registry path are:
# projects/<project>/locations/<locations>/repositories/<repo>/packages/<package>/versions/<name>
# projects/<project>/locations/<locations>/repositories/<repo>/packages/<package>/versions/<version_id>@dirsum_sha256:<hex_value>
ar_uri_regex = r'projects/[^/]+/locations/[^/]+/repositories/.*'
if re.match(ar_uri_regex, args.source_uri):
source_uri = args.source_uri
else:
source_uri = six.text_type(resources.REGISTRY.Parse(args.source_uri))
image.rawDisk = messages.Image.RawDiskValue(source=source_uri)
elif args.source_disk:
source_disk_ref = flags.SOURCE_DISK_ARG.ResolveAsResource(
args,
holder.resources,
scope_lister=compute_flags.GetDefaultScopeLister(client),
source_project=args.source_disk_project)
image.sourceDisk = source_disk_ref.SelfLink()
image.sourceDiskEncryptionKey = csek_utils.MaybeLookupKeyMessage(
csek_keys, source_disk_ref, client.apitools_client)
elif hasattr(args, 'source_snapshot') and args.source_snapshot:
source_snapshot_ref = flags.SOURCE_SNAPSHOT_ARG.ResolveAsResource(
args,
holder.resources,
scope_lister=compute_flags.GetDefaultScopeLister(client))
image.sourceSnapshot = source_snapshot_ref.SelfLink()
image.sourceSnapshotEncryptionKey = csek_utils.MaybeLookupKeyMessage(
csek_keys, source_snapshot_ref, client.apitools_client)
if args.licenses:
image.licenses = args.licenses
guest_os_features = getattr(args, 'guest_os_features', [])
if guest_os_features:
guest_os_feature_messages = []
for feature in guest_os_features:
gf_type = messages.GuestOsFeature.TypeValueValuesEnum(feature)
guest_os_feature = messages.GuestOsFeature()
guest_os_feature.type = gf_type
guest_os_feature_messages.append(guest_os_feature)
image.guestOsFeatures = guest_os_feature_messages
initial_state, has_set = image_utils.CreateInitialStateConfig(
args, messages
)
if has_set:
image.shieldedInstanceInitialState = initial_state
if args.IsSpecified('storage_location'):
image.storageLocations = [args.storage_location]
if hasattr(image, 'locked'):
image.locked = args.locked
request = messages.ComputeImagesInsertRequest(
image=image,
project=image_ref.project)
args_labels = getattr(args, 'labels', None)
if args_labels:
labels = messages.Image.LabelsValue(additionalProperties=[
messages.Image.LabelsValue.AdditionalProperty(
key=key, value=value)
for key, value in sorted(six.iteritems(args_labels))])
request.image.labels = labels
# --force is in GA, --force-create is in beta and deprecated.
if args.force or getattr(args, 'force_create', None):
request.forceCreate = True
return client.MakeRequests([(client.apitools_client.images, 'Insert',
request)], timeout=POLL_TIMEOUT)
@base.DefaultUniverseOnly
@base.ReleaseTracks(base.ReleaseTrack.BETA)
class CreateBeta(Create):
"""Create Compute Engine images."""
@classmethod
def Args(cls, parser):
messages = cls._GetApiHolder(no_http=True).client.messages
_Args(
parser,
messages,
supports_force_create=True,
support_user_licenses=True,
supports_rollout_override=False,
support_tags=False,
)
parser.display_info.AddCacheUpdater(flags.ImagesCompleter)
def Run(self, args):
return self._Run(args, support_user_licenses=True, support_tags=False)
@base.DefaultUniverseOnly
@base.ReleaseTracks(base.ReleaseTrack.ALPHA)
class CreateAlpha(Create):
"""Create Compute Engine images."""
@classmethod
def Args(cls, parser):
messages = cls._GetApiHolder(no_http=True).client.messages
_Args(
parser,
messages,
supports_force_create=True,
support_user_licenses=True,
supports_rollout_override=True,
support_tags=True,
)
parser.display_info.AddCacheUpdater(flags.ImagesCompleter)
def Run(self, args):
return self._Run(
args,
support_user_licenses=True,
supports_rollout_override=True,
support_tags=True,
)
Create.detailed_help = {
'brief':
'Create Compute Engine images',
'DESCRIPTION':
"""\
*{command}* is used to create custom disk images.
The resulting image can be provided during instance or disk creation
so that the instance attached to the resulting disks has access
to a known set of software or files from the image.
Images can be created from gzipped compressed tarball containing raw
disk data, existing disks in any zone, existing images, and existing
snapshots inside the same project.
Images are global resources, so they can be used across zones and
projects.
To learn more about creating image tarballs, visit
[](https://cloud.google.com/compute/docs/creating-custom-image).
""",
'EXAMPLES':
"""\
To create an image 'my-image' from a disk 'my-disk' in zone 'us-east1-a', run:
$ {command} my-image --source-disk=my-disk --source-disk-zone=us-east1-a
To create an image 'my-image' from a disk 'my-disk' in zone 'us-east1-a' with source
disk project 'source-disk-project' run:
$ {command} my-image --source-disk=my-disk --source-disk-zone=us-east1-a --source-disk-project=source-disk-project
To create an image 'my-image' from another image 'source-image'
with source image project 'source-image-project', run:
$ {command} my-image --source-image=source-image --source-image-project=source-image-project
To create an image 'my-image' from the latest non-deprecated image in the family 'source-image-family'
with source image project 'source-image-project', run:
$ {command} my-image --source-image-family=source-image-family --source-image-project=source-image-project
To create an image 'my-image' from a snapshot 'source-snapshot', run:
$ {command} my-image --source-snapshot=source-snapshot
""",
}
CreateBeta.detailed_help = Create.detailed_help

View File

@@ -0,0 +1,66 @@
# -*- coding: utf-8 -*- #
# Copyright 2014 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 deleting 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 utils
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.compute import flags as compute_flags
from googlecloudsdk.command_lib.compute.images import flags
class Delete(base.DeleteCommand):
"""Delete Compute Engine images."""
@staticmethod
def Args(parser):
Delete.DiskImageArg = flags.MakeDiskImageArg(plural=True)
Delete.DiskImageArg.AddArgument(parser, operation_type='delete')
parser.display_info.AddCacheUpdater(flags.ImagesCompleter)
def Run(self, args):
holder = base_classes.ComputeApiHolder(self.ReleaseTrack())
client = holder.client
image_refs = Delete.DiskImageArg.ResolveAsResource(
args,
holder.resources,
scope_lister=compute_flags.GetDefaultScopeLister(client))
utils.PromptForDeletion(image_refs)
requests = []
for image_ref in image_refs:
requests.append((client.apitools_client.images, 'Delete',
client.messages.ComputeImagesDeleteRequest(
**image_ref.AsDict())))
return client.MakeRequests(requests)
Delete.detailed_help = {
'DESCRIPTION':
'*{command}* deletes one or more Compute Engine images.',
'EXAMPLES':
"""
To delete images 'my-image1' and 'my-image2', run:
$ {command} my-image1 my-image2
""",
}

View File

@@ -0,0 +1,231 @@
# -*- coding: utf-8 -*- #
# Copyright 2014 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 deprecating images."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import datetime
from googlecloudsdk.api_lib.compute import base_classes
from googlecloudsdk.calliope import arg_parsers
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.compute.images import flags
def _ResolveTime(absolute, relative_sec, current_time):
"""Get the RFC 3339 time string for a provided absolute or relative time."""
if absolute:
# TODO(b/36057353): It's unfortunate that datetime.datetime cannot
# parse from RFC 3339, but it can output to it. It would be
# super cool if we could verify the validity of the user's
# input here and fail fast if an invalid date/time is given.
# For now, I assume that the user's input is valid.
return absolute
elif relative_sec:
return (
current_time + datetime.timedelta(seconds=relative_sec)
).replace(microsecond=0).isoformat()
else:
return None
class DeprecateImages(base.SilentCommand):
"""Manage deprecation status of Compute Engine images.
*{command}* is used to deprecate images.
"""
@staticmethod
def Args(parser):
DeprecateImages.DISK_IMAGE_ARG = flags.MakeDiskImageArg()
DeprecateImages.DISK_IMAGE_ARG.AddArgument(parser)
flags.REPLACEMENT_DISK_IMAGE_ARG.AddArgument(parser)
deprecation_statuses = {
'ACTIVE': 'The image is currently supported.',
'DELETED': (
'New uses result in an error. Setting this state will not '
'automatically delete the image. You must still make a request to '
'delete the image to remove it from the image list.'),
'DEPRECATED': (
'Operations which create a new *DEPRECATED* resource return '
'successfully, but with a warning indicating that the image is '
'deprecated and recommending its replacement.'),
'OBSOLETE': 'New uses result in an error.',
}
parser.add_argument(
'--state',
choices=deprecation_statuses,
default='ACTIVE',
type=lambda x: x.upper(),
required=True,
help='The deprecation state to set on the image.')
deprecate_group = parser.add_mutually_exclusive_group()
deprecate_group.add_argument(
'--deprecate-on',
help="""\
Specifies a date when the image should be marked as DEPRECATED.
Note: This is only informational and the image will not be deprecated unless you manually deprecate it.
This flag is mutually exclusive with *--deprecate-in*.
The date and time specified must be valid RFC 3339 full-date or date-time.
For times in UTC, this looks like ``YYYY-MM-DDTHH:MM:SSZ''.
For example: 2020-01-02T00:00:00Z for midnight on January 2, 2020 in UTC.
""")
deprecate_group.add_argument(
'--deprecate-in',
type=arg_parsers.Duration(),
help="""\
Specifies a time duration in which the image should be marked as ``DEPRECATED''.
Note: This is only informational and the image will not be deprecated unless you manually deprecate it.
This flag is mutually exclusive with *--deprecate-on*.
For example, specifying ``30d'' sets the planned ``DEPRECATED'' date to 30 days from the current system time,
but does not deprecate the image. You must manually deprecate the image in 30 days.
See $ gcloud topic datetimes for information on duration formats.
""")
delete_group = parser.add_mutually_exclusive_group()
delete_group.add_argument(
'--delete-on',
help="""\
Specifies a date when the image should be marked as ``DELETED''.
Note: This is only informational and the image will not be deleted unless you manually delete it.
This flag is mutually exclusive with *--delete-in*.
The date and time specified must be valid RFC 3339 full-date or date-time.
For times in UTC, this looks like ``YYYY-MM-DDTHH:MM:SSZ''.
For example: 2020-01-02T00:00:00Z for midnight on January 2, 2020 in UTC.
""")
delete_group.add_argument(
'--delete-in',
type=arg_parsers.Duration(),
help="""\
Specifies a time duration in which the image should be marked as ``DELETED''.
Note: This is only informational and the image will not be deleted unless you manually delete it.
For example, specifying ``30d'' sets the planned ``DELETED'' time to 30 days from the current system time,
but does not delete the image. You must manually delete the image in 30 days.
See $ gcloud topic datetimes for information on duration formats.
This flag is mutually exclusive with *--delete-on*.
""")
obsolete_group = parser.add_mutually_exclusive_group()
obsolete_group.add_argument(
'--obsolete-on',
help="""\
Specifies a date when the image should be marked as ``OBSOLETE''.
Note: This is only informational and the image will not be obsoleted unless you manually obsolete it.
This flag is mutually exclusive with *--obsolete-in*.
The date and time specified must be valid RFC 3339 full-date or date-time.
For times in UTC, this looks like ``YYYY-MM-DDTHH:MM:SSZ''.
For example: 2020-01-02T00:00:00Z for midnight on January 2, 2020 in UTC.
""")
obsolete_group.add_argument(
'--obsolete-in',
type=arg_parsers.Duration(),
help="""\
Specifies a time duration in which the image should be marked as ``OBSOLETE''.
Note: This is only informational and the image will not be obsoleted unless you manually obsolete it.
This flag is mutually exclusive with *--obsolete-on*.
For example, specifying ``30d'' sets the planned ``OBSOLETE'' time to 30 days from the current system time,
but does not obsolete the image. You must manually obsolete the image in 30 days.
See $ gcloud topic datetimes for information on duration formats.
""")
def Run(self, args):
"""Invokes requests necessary for deprecating images."""
# TODO(b/13695932): Note that currently there is a bug in the backend
# whereby any request other than a completely empty request or a request
# with state set to something other than ACTIVE will fail.
# GCloud will be able to be made more permissive w.r.t. the checks
# below when the API changes.
holder = base_classes.ComputeApiHolder(self.ReleaseTrack())
client = holder.client
# Determine the date and time to deprecate for each flag set.
current_time = datetime.datetime.now()
delete_time = _ResolveTime(args.delete_on, args.delete_in, current_time)
obsolete_time = _ResolveTime(
args.obsolete_on, args.obsolete_in, current_time)
deprecate_time = _ResolveTime(
args.deprecate_on, args.deprecate_in, current_time)
state = client.messages.DeprecationStatus.StateValueValuesEnum(args.state)
replacement_ref = flags.REPLACEMENT_DISK_IMAGE_ARG.ResolveAsResource(
args, holder.resources)
if replacement_ref:
replacement_uri = replacement_ref.SelfLink()
else:
replacement_uri = None
image_ref = DeprecateImages.DISK_IMAGE_ARG.ResolveAsResource(
args, holder.resources)
request = client.messages.ComputeImagesDeprecateRequest(
deprecationStatus=client.messages.DeprecationStatus(
state=state,
deleted=delete_time,
obsolete=obsolete_time,
deprecated=deprecate_time,
replacement=replacement_uri),
image=image_ref.Name(),
project=image_ref.project)
return client.MakeRequests([(client.apitools_client.images,
'Deprecate', request)])
DeprecateImages.detailed_help = {
'EXAMPLES': """
To deprecate an image called 'IMAGE' immediately, mark it as
obsolete in one day, and mark it as deleted in two days, use:
$ {command} IMAGE --state=DEPRECATED --obsolete-in=1d --delete-in=2d
To un-deprecate an image called 'IMAGE' and clear times for deprecated,
obsoleted, and deleted, use:
$ {command} IMAGE --state=ACTIVE
""",
}

View File

@@ -0,0 +1,56 @@
# -*- coding: utf-8 -*- #
# Copyright 2014 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 describing 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.calliope import base
from googlecloudsdk.command_lib.compute import flags as compute_flags
from googlecloudsdk.command_lib.compute.images import flags
class Describe(base.DescribeCommand):
"""Describe a Compute Engine image."""
@staticmethod
def Args(parser):
Describe.DiskImageArg = flags.MakeDiskImageArg()
Describe.DiskImageArg.AddArgument(parser, operation_type='describe')
def Run(self, args):
holder = base_classes.ComputeApiHolder(self.ReleaseTrack())
client = holder.client
image_ref = Describe.DiskImageArg.ResolveAsResource(
args,
holder.resources,
scope_lister=compute_flags.GetDefaultScopeLister(client))
request = client.messages.ComputeImagesGetRequest(**image_ref.AsDict())
return client.MakeRequests([(client.apitools_client.images, 'Get',
request)])[0]
Describe.detailed_help = {
'brief': 'Describe a Compute Engine image',
'DESCRIPTION': """
*{command}* displays all data associated with a Compute Engine
image in a project.
""",
}

View File

@@ -0,0 +1,94 @@
# -*- coding: utf-8 -*- #
# Copyright 2015 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 getting the latest image from a family."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.compute import base_classes
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.compute import completers
from googlecloudsdk.command_lib.compute import flags as compute_flags
from googlecloudsdk.command_lib.compute.images import flags
class DescribeFromFamily(base.DescribeCommand):
"""Describe the latest image from an image family.
*{command}* looks up the latest image from an image family and runs a describe
on it.
"""
@staticmethod
def Args(parser):
DescribeFromFamily.DiskImageArg = flags.MakeDiskImageArg()
DescribeFromFamily.DiskImageArg.AddArgument(
parser, operation_type='describe')
# Do not use compute_flags.AddZoneFlag() because there should be no
# interaction with the compute/zone property.
parser.add_argument(
'--zone',
completer=completers.ZonesCompleter,
help=('Zone to query. Returns the latest image available in the image '
'family for the specified zone. If not specified, returns the '
'latest globally available image.'))
def Run(self, args):
holder = base_classes.ComputeApiHolder(self.ReleaseTrack())
client = holder.client
image_ref = DescribeFromFamily.DiskImageArg.ResolveAsResource(
args,
holder.resources,
scope_lister=compute_flags.GetDefaultScopeLister(client))
family = image_ref.image
if family.startswith('family/'):
family = family[len('family/'):]
if hasattr(args, 'zone') and args.zone:
request = client.messages.ComputeImageFamilyViewsGetRequest(
family=family, project=image_ref.project, zone=args.zone)
return client.MakeRequests([(client.apitools_client.imageFamilyViews,
'Get', request)])[0]
else:
request = client.messages.ComputeImagesGetFromFamilyRequest(
family=family, project=image_ref.project)
return client.MakeRequests([(client.apitools_client.images,
'GetFromFamily', request)])[0]
DescribeFromFamily.detailed_help = {
'brief':
'Describe the latest image from an image family.',
'DESCRIPTION':
"""\
*{command}* looks up the latest image from an image family and runs a
describe on it. If the image is not in the default project, you need to
specify a value for `--project`.
""",
'EXAMPLES':
"""\
To view the description for the latest ``debian-9'' image from the
``debian-cloud'' project, run:
$ {command} debian-9 --project=debian-cloud
""",
}

View File

@@ -0,0 +1,241 @@
# -*- coding: utf-8 -*- #
# Copyright 2018 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.
"""Export image command."""
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 daisy_utils
from googlecloudsdk.api_lib.compute import image_utils
from googlecloudsdk.api_lib.storage import storage_api
from googlecloudsdk.api_lib.storage import storage_util
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.command_lib.compute.images import flags
from googlecloudsdk.core import properties
from googlecloudsdk.core import resources as core_resources
_OUTPUT_FILTER = ['[Daisy', '[image-export', ' image', 'ERROR']
@base.DefaultUniverseOnly
@base.ReleaseTracks(base.ReleaseTrack.GA)
class Export(base.CreateCommand):
"""Export a Compute Engine image."""
@staticmethod
def Args(parser):
image_group = parser.add_mutually_exclusive_group(required=True)
image_group.add_argument(
'--image',
help='The name of the disk image to export.',
)
image_group.add_argument(
'--image-family',
help=('The family of the disk image to be exported. When a family '
'is used instead of an image, the latest non-deprecated image '
'associated with that family is used.'),
)
image_utils.AddImageProjectFlag(parser)
flags.compute_flags.AddZoneFlag(
parser, 'image', 'export',
help_text='The zone to use when exporting the image. When you export '
'an image, the export tool creates and uses temporary VMs '
'in your project for the export process. Use this flag to '
'specify the zone to use for these temporary VMs.')
parser.add_argument(
'--destination-uri',
required=True,
help=('The Cloud Storage URI destination for '
'the exported virtual disk file.'),
)
# Export format can take more values than what we list here in the help.
# However, we don't want to suggest formats that will likely never be used,
# so we list common ones here, but don't prevent others from being used.
parser.add_argument(
'--export-format',
help=('Specify the format to export to, such as '
'`vmdk`, `vhdx`, `vpc`, or `qcow2`.'),
)
parser.add_argument(
'--network',
help=('The name of the network in your project to use for the image '
'export. When you export an image, the export tool creates and '
'uses temporary VMs in your project for the export process. Use '
'this flag to specify the network to use for these temporary VMs.'
),
)
parser.add_argument(
'--subnet',
help="""\
Name of the subnetwork in your project to use for the image export. When
you export an image, the export tool creates and uses temporary VMs in
your project for the export process. Use this flag to specify the
subnetwork to use for these temporary VMs.
* If the network resource is in legacy mode, do not provide this
property.
* If the network is in auto subnet mode, specifying the subnetwork is
optional.
* If the network is in custom subnet mode, then this field must be
specified.
"""
)
daisy_utils.AddComputeServiceAccountArg(
parser, 'image export',
daisy_utils.EXPORT_ROLES_FOR_COMPUTE_SERVICE_ACCOUNT)
daisy_utils.AddCloudBuildServiceAccountArg(
parser,
'image export',
daisy_utils.EXPORT_ROLES_FOR_CLOUDBUILD_SERVICE_ACCOUNT,
)
daisy_utils.AddCommonDaisyArgs(
parser,
operation='an export',
extra_timeout_help=("""
If you are exporting a large image that takes longer than 24 hours to
export, either use the RAW disk format to reduce the time needed for
converting the image, or split the data into several smaller images.
"""))
parser.display_info.AddCacheUpdater(flags.ImagesCompleter)
def Run(self, args):
try:
gcs_uri = daisy_utils.MakeGcsObjectUri(args.destination_uri)
except (storage_util.InvalidObjectNameError,
core_resources.UnknownCollectionException):
raise exceptions.InvalidArgumentException(
'destination-uri',
'must be a path to an object in Google Cloud Storage')
tags = ['gce-daisy-image-export']
export_args = []
daisy_utils.AppendNetworkAndSubnetArgs(args, export_args)
daisy_utils.AppendArg(export_args, 'zone',
properties.VALUES.compute.zone.Get())
daisy_utils.AppendArg(export_args, 'scratch_bucket_gcs_path',
'gs://{0}/'.format(self._GetDaisyBucket(args)))
daisy_utils.AppendArg(export_args, 'timeout',
'{}s'.format(daisy_utils.GetDaisyTimeout(args)))
daisy_utils.AppendArg(export_args, 'client_id', 'gcloud')
source_image = self._GetSourceImage(args.image, args.image_family,
args.image_project)
daisy_utils.AppendArg(export_args, 'source_image', source_image)
daisy_utils.AppendArg(export_args, 'destination_uri', gcs_uri)
if args.export_format:
daisy_utils.AppendArg(export_args, 'format', args.export_format.lower())
if 'compute_service_account' in args:
daisy_utils.AppendArg(export_args, 'compute_service_account',
args.compute_service_account)
return self._RunImageExport(args, export_args, tags, _OUTPUT_FILTER)
def _RunImageExport(self, args, export_args, tags, output_filter):
return daisy_utils.RunImageExport(
args,
export_args,
tags,
_OUTPUT_FILTER,
release_track=self.ReleaseTrack().id.lower()
if self.ReleaseTrack() else None)
def _GetSourceImage(self, image, image_family, image_project):
holder = base_classes.ComputeApiHolder(self.ReleaseTrack())
client = holder.client
resources = holder.resources
project = properties.VALUES.core.project.GetOrFail()
image_expander = image_utils.ImageExpander(client, resources)
image = image_expander.ExpandImageFlag(
user_project=project, image=image, image_family=image_family,
image_project=image_project, return_image_resource=False)
image_ref = resources.Parse(image[0], collection='compute.images')
return image_ref.RelativeName()
@staticmethod
def _GetDaisyBucket(args):
storage_client = storage_api.StorageClient()
bucket_location = storage_client.GetBucketLocationForFile(
args.destination_uri)
return daisy_utils.CreateDaisyBucketInProject(
bucket_location,
storage_client,
enable_uniform_level_access=True,
soft_delete_duration=0)
@base.ReleaseTracks(base.ReleaseTrack.BETA)
class ExportBeta(Export):
"""Export a Compute Engine image for Beta release track."""
@classmethod
def Args(cls, parser):
super(ExportBeta, cls).Args(parser)
daisy_utils.AddExtraCommonDaisyArgs(parser)
def _RunImageExport(self, args, export_args, tags, output_filter):
return daisy_utils.RunImageExport(
args,
export_args,
tags,
_OUTPUT_FILTER,
release_track=self.ReleaseTrack().id.lower()
if self.ReleaseTrack() else None,
docker_image_tag=args.docker_image_tag)
@base.ReleaseTracks(base.ReleaseTrack.ALPHA)
class ExportAlpha(ExportBeta):
"""Export a Compute Engine image for Alpha release track."""
Export.detailed_help = {
'brief':
'Export a Compute Engine image',
'DESCRIPTION':
"""\
*{command}* exports virtual disk images from Compute Engine.
By default, images are exported in the Compute Engine format,
which is a `disk.raw` file that is tarred and gzipped.
The `--export-format` flag exports the image to a format supported
by QEMU using qemu-img. Valid formats include `vmdk`, `vhdx`, `vpc`,
`vdi`, and `qcow2`.
Before exporting an image, set up access to Cloud Storage and grant
required roles to the user accounts and service accounts. For more
information, see [](https://cloud.google.com/compute/docs/import/requirements-export-import-images).
""",
'EXAMPLES':
"""\
To export a VMDK file ``my-image'' from a project ``my-project'' to a
Cloud Storage bucket ``my-bucket'', run:
$ {command} --image=my-image --destination-uri=gs://my-bucket/my-image.vmdk --export-format=vmdk --project=my-project
"""
}

View File

@@ -0,0 +1,31 @@
release_tracks: [ALPHA, BETA, GA]
help_text:
brief: Get the IAM policy for a Compute Engine image.
description: |
*{command}* displays the IAM policy associated with a
Compute Engine image in a project. If formatted as JSON,
the output can be edited and used as a policy file for
set-iam-policy. The output includes an "etag" field
identifying the version emitted and allowing detection of
concurrent policy updates; see
$ {parent} set-iam-policy for additional details.
examples: |
To print the IAM policy for a given image, run:
$ {command} my-image
request:
collection: compute.images
use_relative_name: false
modify_request_hooks:
- googlecloudsdk.command_lib.iam.hooks:UseMaxRequestedPolicyVersion:api_field=optionsRequestedPolicyVersion
api_version: v1
BETA:
api_version: beta
ALPHA:
api_version: alpha
arguments:
resource:
help_text: The image to display the IAM policy for.
spec: !REF googlecloudsdk.command_lib.compute.resources:image

View File

@@ -0,0 +1,792 @@
# -*- 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.
"""Import image command."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import abc
import enum
import os.path
import string
import uuid
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 utils
from googlecloudsdk.api_lib.storage import storage_api
from googlecloudsdk.api_lib.storage import storage_util
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.command_lib.compute.images import flags
from googlecloudsdk.command_lib.compute.images import os_choices
from googlecloudsdk.core import log
from googlecloudsdk.core import properties
from googlecloudsdk.core import resources
from googlecloudsdk.core.console import progress_tracker
import six
_WORKFLOWS_URL = (
'https://github.com/GoogleCloudPlatform/compute-image-import/'
'tree/main/daisy_workflows/image_import'
)
_OUTPUT_FILTER = [
'[Daisy',
'[import-',
'[onestep-',
'starting build',
' import',
'ERROR',
]
class CloudProvider(enum.Enum):
UNKNOWN = 0
AWS = 1
def _HasAwsArgs(args):
return (
args.aws_access_key_id
or args.aws_secret_access_key
or args.aws_session_token
or args.aws_region
or args.aws_ami_id
or args.aws_ami_export_location
or args.aws_source_ami_file_path
)
def _HasExternalCloudProvider(args):
return _GetExternalCloudProvider(args) != CloudProvider.UNKNOWN
def _GetExternalCloudProvider(args):
if _HasAwsArgs(args):
return CloudProvider.AWS
return CloudProvider.UNKNOWN
def _AppendTranslateWorkflowArg(args, import_args):
if args.os:
daisy_utils.AppendArg(import_args, 'os', args.os)
daisy_utils.AppendArg(
import_args, 'custom_translate_workflow', args.custom_workflow
)
def _AppendAwsArgs(args, import_args):
"""Appends args related to AWS image import."""
daisy_utils.AppendArg(
import_args, 'aws_access_key_id', args.aws_access_key_id
)
daisy_utils.AppendArg(
import_args, 'aws_secret_access_key', args.aws_secret_access_key
)
daisy_utils.AppendArg(
import_args, 'aws_session_token', args.aws_session_token
)
daisy_utils.AppendArg(import_args, 'aws_region', args.aws_region)
if args.aws_ami_id:
daisy_utils.AppendArg(import_args, 'aws_ami_id', args.aws_ami_id)
if args.aws_ami_export_location:
daisy_utils.AppendArg(
import_args, 'aws_ami_export_location', args.aws_ami_export_location
)
if args.aws_source_ami_file_path:
daisy_utils.AppendArg(
import_args, 'aws_source_ami_file_path', args.aws_source_ami_file_path
)
def _CheckImageName(image_name):
"""Checks for a valid GCE image name."""
name_message = (
'Name must start with a lowercase letter followed by up to '
'63 lowercase letters, numbers, or hyphens, and cannot end '
'with a hyphen.'
)
name_ok = True
valid_chars = string.digits + string.ascii_lowercase + '-'
if len(image_name) > 64:
name_ok = False
elif image_name[0] not in string.ascii_lowercase:
name_ok = False
elif not all(char in valid_chars for char in image_name):
name_ok = False
elif image_name[-1] == '-':
name_ok = False
if not name_ok:
raise exceptions.InvalidArgumentException('IMAGE_NAME', name_message)
def _CheckForExistingImage(
image_name, compute_holder, arg_name='IMAGE_NAME', expect_to_exist=False
):
"""Check if image already exists."""
# Don't perform a check for image name used in E2E test as passing an invalid
# name to the backend is currently the only way to perform a quick sanity E2E
# check on the backend. Alternative is not to perform the check on whether
# image exists at all which would lead to worse customer experience. No
# security issue with this as the backend does the same check on whether an
# image exists or not.
expect_to_exist_image_name_exclusions = ['sample-image-123']
if expect_to_exist and image_name in expect_to_exist_image_name_exclusions:
return
image_ref = resources.REGISTRY.Parse(
image_name,
collection='compute.images',
params={'project': properties.VALUES.core.project.GetOrFail},
)
image_expander = image_utils.ImageExpander(
compute_holder.client, compute_holder.resources
)
try:
_ = image_expander.GetImage(image_ref)
image_exists = True
except utils.ImageNotFoundError:
image_exists = False
if not expect_to_exist and image_exists:
message = 'The image [{0}] already exists.'.format(image_name)
raise exceptions.InvalidArgumentException(arg_name, message)
elif expect_to_exist and not image_exists:
message = 'The image [{0}] does not exist.'.format(image_name)
raise exceptions.InvalidArgumentException(arg_name, message)
@base.ReleaseTracks(base.ReleaseTrack.GA)
@base.Deprecate(
is_removed=False,
warning=(
'This command is being deprecated. Instead, use the `gcloud migration'
' vms image-imports` command. For more information, see https://'
'cloud.google.com/migrate/virtual-machines/docs/5.0/migrate/'
'image_import.'
),
error=(
'This command hash been deprecated. Instead, use the `gcloud migration'
' vms image-imports` command. For more information, see https://'
'cloud.google.com/migrate/virtual-machines/docs/5.0/migrate/'
'image_import.'
),
)
@base.DefaultUniverseOnly
class Import(base.CreateCommand):
"""Import an image into Compute Engine."""
_OS_CHOICES = os_choices.OS_CHOICES_IMAGE_IMPORT_GA
def __init__(self, *args, **kwargs):
self.storage_client = storage_api.StorageClient()
super(Import, self).__init__(*args, **kwargs)
@classmethod
def Args(cls, parser):
compute_holder = cls._GetComputeApiHolder(no_http=True)
compute_client = compute_holder.client
messages = compute_client.messages
Import.DISK_IMAGE_ARG = flags.MakeDiskImageArg()
Import.DISK_IMAGE_ARG.AddArgument(parser, operation_type='create')
flags.compute_flags.AddZoneFlag(
parser,
'image',
'import',
help_text=(
'Zone to use when importing the image. When you import '
'an image, the import tool creates and uses temporary VMs '
'in your project for the import process. Use this flag to '
'specify the zone to use for these temporary VMs.'
),
)
source = parser.add_mutually_exclusive_group(required=True, sort_args=False)
import_from_local_or_gcs = source.add_mutually_exclusive_group(
help=(
'Image import from local file, Cloud Storage or '
'Compute Engine image.'
)
)
import_from_local_or_gcs.add_argument(
'--source-file',
help=("""A local file, or the Cloud Storage URI of the virtual
disk file to import. For example: ``gs://my-bucket/my-image.vmdk''
or ``./my-local-image.vmdk''. For more information about Cloud
Storage URIs, see
https://cloud.google.com/storage/docs/request-endpoints#json-api.
"""),
)
flags.SOURCE_IMAGE_ARG.AddArgument(
import_from_local_or_gcs, operation_type='import'
)
import_from_aws = source.add_group(help='Image import from AWS.')
daisy_utils.AddAWSImageImportSourceArgs(import_from_aws)
image_utils.AddGuestOsFeaturesArgForImport(parser, messages)
workflow = parser.add_mutually_exclusive_group()
os_group = workflow.add_group()
daisy_utils.AddByolArg(os_group)
os_group.add_argument(
'--os',
choices=sorted(cls._OS_CHOICES),
help='Specifies the OS of the disk image being imported.',
)
workflow.add_argument(
'--data-disk',
help=(
'Specifies that the disk has no bootable OS installed on it. '
'Imports the disk without making it bootable or installing '
'Google tools on it.'
),
action='store_true',
)
workflow.add_argument(
'--custom-workflow',
help=(
"""\
Specifies a custom workflow to use for image translation. Workflow
should be relative to the image_import directory here: []({0}).
For example: `debian/translate_debian_9.wf.json'""".format(
_WORKFLOWS_URL
)
),
hidden=True,
)
daisy_utils.AddCommonDaisyArgs(
parser,
operation='an import',
extra_timeout_help=("""
If you are importing a large image that takes longer than 24 hours to
import, either use the RAW disk format to reduce the time needed for
converting the image, or split the data into several smaller images.
"""),
)
parser.add_argument(
'--guest-environment',
action='store_true',
default=True,
help=(
'Installs the guest environment on the image.'
' See '
'https://cloud.google.com/compute/docs/images/guest-environment.'
),
)
parser.add_argument(
'--network',
help=(
'Name of the network in your project to use for the image '
'import. When you import an image, the import tool creates and '
'uses temporary VMs in your project for the import process. Use '
'this flag to specify the network to use for these temporary VMs.'
),
)
parser.add_argument(
'--subnet',
help=("""\
Name of the subnetwork in your project to use for the image import. When
you import an image, the import tool creates and uses temporary VMs in
your project for the import process. Use this flag to specify the
subnetwork to use for these temporary VMs.
* If the network resource is in legacy mode, do not provide this
property.
* If the network is in auto subnet mode, specifying the subnetwork is
optional.
* If the network is in custom subnet mode, then this field must be
specified.
"""),
)
parser.add_argument(
'--family', help='Family to set for the imported image.'
)
parser.add_argument(
'--description', help='Description to set for the imported image.'
)
parser.display_info.AddCacheUpdater(flags.ImagesCompleter)
parser.add_argument(
'--storage-location',
help="""\
Specifies a Cloud Storage location, either regional or multi-regional,
where image content is to be stored. If not specified, the multi-region
location closest to the source is chosen automatically.
""",
)
parser.add_argument(
'--sysprep-windows',
action='store_true',
hidden=True,
help='Whether to generalize the image using Windows Sysprep.',
)
daisy_utils.AddNoAddressArg(
parser,
'image import',
'https://cloud.google.com/compute/docs/import/importing-virtual-disks#no-external-ip',
)
daisy_utils.AddComputeServiceAccountArg(
parser,
'image import',
daisy_utils.IMPORT_ROLES_FOR_COMPUTE_SERVICE_ACCOUNT,
)
daisy_utils.AddCloudBuildServiceAccountArg(
parser,
'image import',
daisy_utils.IMPORT_ROLES_FOR_CLOUDBUILD_SERVICE_ACCOUNT,
)
parser.add_argument(
'--cmd-deprecated',
action='store_true',
required=True,
help="""
The command you're using is deprecated and will be removed by December 31,
2025. We recommend using `gcloud compute migration image-imports` instead.
See our official documentation for more information.
https://cloud.google.com/migrate/virtual-machines/docs/5.0/migrate/image_import.
""",
)
@classmethod
def _GetComputeApiHolder(cls, no_http=False):
return base_classes.ComputeApiHolder(cls.ReleaseTrack(), no_http)
def Run(self, args):
compute_holder = self._GetComputeApiHolder()
# Fail early if the requested image name is invalid or already exists.
_CheckImageName(args.image_name)
_CheckForExistingImage(args.image_name, compute_holder)
stager = self._CreateImportStager(args, compute_holder)
import_metadata = stager.Stage()
log.warning('Importing image. This may take up to 2 hours.')
tags = ['gce-daisy-image-import']
return self._RunImageImport(args, import_metadata, tags, _OUTPUT_FILTER)
def _RunImageImport(self, args, import_args, tags, output_filter):
image_tag = daisy_utils.GetDefaultBuilderVersion()
if hasattr(args, 'docker_image_tag'):
image_tag = args.docker_image_tag
if _HasExternalCloudProvider(args):
return daisy_utils.RunOnestepImageImport(
args,
import_args,
tags,
_OUTPUT_FILTER,
release_track=self.ReleaseTrack().id.lower()
if self.ReleaseTrack()
else None,
docker_image_tag=image_tag,
)
return daisy_utils.RunImageImport(
args,
import_args,
tags,
_OUTPUT_FILTER,
release_track=self.ReleaseTrack().id.lower()
if self.ReleaseTrack()
else None,
docker_image_tag=image_tag,
)
def _CreateImportStager(self, args, compute_holder):
if _HasExternalCloudProvider(args):
return ImportFromExternalCloudProviderStager(
self.storage_client, compute_holder, args
)
if args.source_image:
return ImportFromImageStager(self.storage_client, compute_holder, args)
if daisy_utils.IsLocalFile(args.source_file):
return ImportFromLocalFileStager(
self.storage_client, compute_holder, args
)
try:
gcs_uri = daisy_utils.MakeGcsObjectUri(args.source_file)
except storage_util.InvalidObjectNameError:
raise exceptions.InvalidArgumentException(
'source-file', 'must be a path to an object in Google Cloud Storage'
)
else:
return ImportFromGSFileStager(
self.storage_client, compute_holder, args, gcs_uri
)
@six.add_metaclass(abc.ABCMeta)
class BaseImportStager(object):
"""Base class for image import stager.
An abstract class which is responsible for preparing import parameters, such
as Daisy parameters and workflow, as well as creating Daisy scratch bucket in
the appropriate location.
"""
def __init__(self, storage_client, compute_holder, args):
self.storage_client = storage_client
self.compute_holder = compute_holder
self.args = args
self.daisy_bucket = self.GetAndCreateDaisyBucket()
def Stage(self):
"""Prepares for import args.
It supports running new import wrapper (gce_vm_image_import).
Returns:
import_args - array of strings, import args.
"""
import_args = []
messages = self.compute_holder.client.messages
daisy_utils.AppendArg(
import_args, 'zone', properties.VALUES.compute.zone.Get()
)
if self.args.storage_location:
daisy_utils.AppendArg(
import_args, 'storage_location', self.args.storage_location
)
daisy_utils.AppendArg(
import_args,
'scratch_bucket_gcs_path',
'gs://{0}/'.format(self.daisy_bucket),
)
daisy_utils.AppendArg(
import_args,
'timeout',
'{}s'.format(daisy_utils.GetDaisyTimeout(self.args)),
)
daisy_utils.AppendArg(import_args, 'client_id', 'gcloud')
daisy_utils.AppendArg(import_args, 'image_name', self.args.image_name)
daisy_utils.AppendBoolArg(
import_args, 'no_guest_environment', not self.args.guest_environment
)
daisy_utils.AppendNetworkAndSubnetArgs(self.args, import_args)
daisy_utils.AppendArg(import_args, 'description', self.args.description)
daisy_utils.AppendArg(import_args, 'family', self.args.family)
if 'byol' in self.args:
daisy_utils.AppendBoolArg(import_args, 'byol', self.args.byol)
# 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(self.args, 'guest_os_features', None) or ()
uefi_compatible = (
messages.GuestOsFeature.TypeValueValuesEnum.UEFI_COMPATIBLE.name
in guest_os_features
)
if uefi_compatible:
daisy_utils.AppendBoolArg(import_args, 'uefi_compatible', True)
if 'sysprep_windows' in self.args:
daisy_utils.AppendBoolArg(
import_args, 'sysprep_windows', self.args.sysprep_windows
)
if 'no_address' in self.args:
daisy_utils.AppendBoolArg(
import_args, 'no_external_ip', self.args.no_address
)
if 'compute_service_account' in self.args:
daisy_utils.AppendArg(
import_args,
'compute_service_account',
self.args.compute_service_account,
)
return import_args
def GetAndCreateDaisyBucket(self):
return daisy_utils.CreateDaisyBucketInProject(
self.GetBucketLocation(),
self.storage_client,
enable_uniform_level_access=True,
soft_delete_duration=0,
)
def GetBucketLocation(self):
if self.args.storage_location:
return self.args.storage_location
return None
class ImportFromExternalCloudProviderStager(BaseImportStager):
"""Image import stager from an external cloud provider."""
def Stage(self):
import_args = []
_AppendAwsArgs(self.args, import_args)
_AppendTranslateWorkflowArg(self.args, import_args)
import_args.extend(
super(ImportFromExternalCloudProviderStager, self).Stage()
)
return import_args
def GetBucketLocation(self):
if self.args.zone:
return daisy_utils.GetRegionFromZone(self.args.zone)
return super(
ImportFromExternalCloudProviderStager, self
).GetBucketLocation()
class ImportFromImageStager(BaseImportStager):
"""Image import stager from an existing image."""
def Stage(self):
_CheckForExistingImage(
self.args.source_image,
self.compute_holder,
arg_name='source-image',
expect_to_exist=True,
)
import_args = []
daisy_utils.AppendArg(import_args, 'source_image', self.args.source_image)
if self.args.data_disk:
daisy_utils.AppendBoolArg(import_args, 'data_disk', self.args.data_disk)
else:
_AppendTranslateWorkflowArg(self.args, import_args)
import_args.extend(super(ImportFromImageStager, self).Stage())
return import_args
def _GetSourceImage(self):
ref = resources.REGISTRY.Parse(
self.args.source_image,
collection='compute.images',
params={'project': properties.VALUES.core.project.GetOrFail},
)
# source_name should be of the form 'global/images/image-name'.
source_name = ref.RelativeName()[len(ref.Parent().RelativeName() + '/') :]
return source_name
def GetBucketLocation(self):
if self.args.zone:
return daisy_utils.GetRegionFromZone(self.args.zone)
return super(ImportFromImageStager, self).GetBucketLocation()
class BaseImportFromFileStager(BaseImportStager):
"""Abstract image import stager for import from a file."""
def Stage(self):
self._FileStage()
import_args = []
# Import and (maybe) translate from the scratch bucket.
daisy_utils.AppendArg(import_args, 'source_file', self.gcs_uri)
if self.args.data_disk:
daisy_utils.AppendBoolArg(import_args, 'data_disk', self.args.data_disk)
else:
_AppendTranslateWorkflowArg(self.args, import_args)
import_args.extend(super(BaseImportFromFileStager, self).Stage())
return import_args
def _FileStage(self):
"""Prepare image file for importing."""
# If the file is an OVA file, print a warning.
if self.args.source_file.lower().endswith('.ova'):
log.warning(
'The specified input file may contain more than one virtual disk. '
'Only the first vmdk disk will be imported. To import a .ova'
"completely, please try 'gcloud compute instances import'"
'instead.'
)
elif self.args.source_file.lower().endswith(
'.tar.gz'
) or self.args.source_file.lower().endswith('.tgz'):
raise exceptions.BadFileException(
'`gcloud compute images import` does not support compressed '
'archives. Please extract your image and try again.\n If you got '
'this file by exporting an image from Compute Engine (e.g., by '
'using `gcloud compute images export`) then you can instead use '
'`gcloud compute images create` to create your image from your '
'.tar.gz file.'
)
self.gcs_uri = self._CopySourceFileToScratchBucket()
@abc.abstractmethod
def _CopySourceFileToScratchBucket(self):
raise NotImplementedError
class ImportFromLocalFileStager(BaseImportFromFileStager):
"""Image import stager from a local file."""
def _CopySourceFileToScratchBucket(self):
return self._UploadToGcs(
self.args.async_, self.args.source_file, self.daisy_bucket, uuid.uuid4()
)
def _UploadToGcs(self, is_async, local_path, daisy_bucket, image_uuid):
"""Uploads a local file to GCS. Returns the gs:// URI to that file."""
file_name = os.path.basename(local_path).replace(' ', '-')
dest_path = 'gs://{0}/tmpimage/{1}-{2}'.format(
daisy_bucket, image_uuid, file_name
)
if is_async:
log.status.Print(
'Async: After upload is complete, your image will be '
'imported from Cloud Storage asynchronously.'
)
with progress_tracker.ProgressTracker(
'Copying [{0}] to [{1}]'.format(local_path, dest_path)
):
return self._UploadToGcsStorageApi(local_path, dest_path)
def _UploadToGcsStorageApi(self, local_path, dest_path):
"""Uploads a local file to Cloud Storage using the gcloud storage api client."""
dest_object = storage_util.ObjectReference.FromUrl(dest_path)
self.storage_client.CopyFileToGCS(local_path, dest_object)
return dest_path
class ImportFromGSFileStager(BaseImportFromFileStager):
"""Image import stager from a file in Cloud Storage."""
def __init__(self, storage_client, compute_holder, args, gcs_uri):
self.source_file_gcs_uri = gcs_uri
super(ImportFromGSFileStager, self).__init__(
storage_client, compute_holder, args
)
def GetBucketLocation(self):
return self.storage_client.GetBucketLocationForFile(
self.source_file_gcs_uri
)
def _CopySourceFileToScratchBucket(self):
image_file = os.path.basename(self.source_file_gcs_uri)
dest_uri = 'gs://{0}/tmpimage/{1}-{2}'.format(
self.daisy_bucket, uuid.uuid4(), image_file
)
src_object = resources.REGISTRY.Parse(
self.source_file_gcs_uri, collection='storage.objects'
)
dest_object = resources.REGISTRY.Parse(
dest_uri, collection='storage.objects'
)
with progress_tracker.ProgressTracker(
'Copying [{0}] to [{1}]'.format(self.source_file_gcs_uri, dest_uri)
):
self.storage_client.Rewrite(src_object, dest_object)
return dest_uri
@base.ReleaseTracks(base.ReleaseTrack.BETA)
class ImportBeta(Import):
"""Import an image into Compute Engine for beta releases."""
_OS_CHOICES = os_choices.OS_CHOICES_IMAGE_IMPORT_BETA
@classmethod
def Args(cls, parser):
super(ImportBeta, cls).Args(parser)
daisy_utils.AddExtraCommonDaisyArgs(parser)
@base.ReleaseTracks(base.ReleaseTrack.ALPHA)
class ImportAlpha(ImportBeta):
"""Import an image into Compute Engine for alpha releases."""
_OS_CHOICES = os_choices.OS_CHOICES_IMAGE_IMPORT_ALPHA
Import.detailed_help = {
'brief': 'Import an image into Compute Engine',
'DESCRIPTION': """
*{command}* imports Virtual Disk images, such as VMWare VMDK files
and VHD files, into Compute Engine.
Importing images involves four steps:
* Upload the virtual disk file to Cloud Storage.
* Import the image to Compute Engine.
* Detect the OS and bootloader contained on the disk.
* Translate the image to make a bootable image.
This command performs all four of these steps as required,
depending on the input arguments specified.
Before importing an image, set up access to Cloud Storage and grant
required roles to the user accounts and service accounts. For more
information, see [](https://cloud.google.com/compute/docs/import/requirements-export-import-images).
To override the detected OS, specify the `--os` flag.
You can omit the translation step using the `--data-disk` flag.
If you exported your disk from Compute Engine then you don't
need to re-import it. Instead, use `{parent_command} create`
to create more images from the disk.
Files stored on Cloud Storage and images in Compute Engine incur
charges. See [](https://cloud.google.com/compute/docs/images/importing-virtual-disks#resource_cleanup).
Troubleshooting: Image import/export tools rely on CloudBuild default
behavior. They has been using the default CloudBuild service account in
order to import/export images to/from Google Cloud Platform. However,
Cloud Build has changed this default behavior and in new projects a
custom user managed service account may need to be provided to perform
the builds. If you get a CloudBuild service account related error, run
gcloud with --cloudbuild-service-account=<custom service account>.
See `gcloud compute images import --help` for details.
""",
'EXAMPLES': """
To import a centos-7 VMDK file, run:
$ {command} myimage-name --os=centos-7 --source-file=mysourcefile
To import a data disk without operating system, run:
$ {command} myimage-name --data-disk --source-file=mysourcefile
""",
}

View File

@@ -0,0 +1,213 @@
# -*- coding: utf-8 -*- #
# Copyright 2014 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 listing 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 constants
from googlecloudsdk.api_lib.compute import lister
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.compute import completers
from googlecloudsdk.command_lib.compute.images import flags
from googlecloudsdk.command_lib.compute.images import policy
from googlecloudsdk.core import properties
from googlecloudsdk.core.universe_descriptor import universe_descriptor
def _PublicImageProjects():
if properties.IsDefaultUniverse():
return sorted(constants.PUBLIC_IMAGE_PROJECTS)
else:
prefix = (
universe_descriptor.UniverseDescriptor()
.Get(properties.GetUniverseDomain())
.project_prefix
)
return [
prefix + ':' + project
for project in sorted(constants.BASE_PUBLIC_IMAGE_PROJECTS)
]
def _Args(parser, support_image_zone_flag=False):
"""Helper function for arguments."""
# GA Args
parser.display_info.AddFormat(flags.LIST_FORMAT)
lister.AddBaseListerArgs(parser)
parser.add_argument(
'--show-deprecated',
action='store_true',
help='If provided, deprecated images are shown.',
)
if constants.PREVIEW_IMAGE_PROJECTS:
preview_image_projects = '{0}.'.format(
', '.join(constants.PREVIEW_IMAGE_PROJECTS)
)
else:
preview_image_projects = '(none)'
parser.add_argument(
'--preview-images',
action='store_true',
default=False,
help="""\
Show images that are in limited preview. The preview image projects
are: {0}
""".format(preview_image_projects),
)
# --show-preview-images for backwards compatibility. --preview-images for
# consistency with --standard-images.
parser.add_argument(
'--show-preview-images',
dest='preview_images',
action='store_true',
hidden=True,
help='THIS ARGUMENT NEEDS HELP TEXT.',
)
parser.add_argument(
'--standard-images',
action='store_true',
default=True,
help="""\
List images from public image projects. The public image projects
that are available include the following: {0}.
""".format(', '.join(constants.PUBLIC_IMAGE_PROJECTS)),
)
# Alpha Args
if support_image_zone_flag:
parser.add_argument(
'--image-zone',
completer=completers.ZonesCompleter,
help=(
'Zone to query. Returns the latest image available in the image '
'family, for the specified zone. If not specified, returns the '
'latest globally available image.'
),
)
@base.UniverseCompatible
@base.ReleaseTracks(base.ReleaseTrack.GA)
class List(base.ListCommand):
"""List Compute Engine images."""
@staticmethod
def Args(parser):
_Args(parser)
def Run(self, args):
return self._Run(args)
def _Run(self, args, support_image_zone_flag=False):
"""Yields images from (potentially) multiple projects."""
holder = base_classes.ComputeApiHolder(self.ReleaseTrack())
client = holder.client
request_data = lister.ParseNamesAndRegexpFlags(args, holder.resources)
def ParseProject(project):
return holder.resources.Parse(
None, {'project': project}, collection='compute.projects'
)
if args.standard_images:
for project in _PublicImageProjects():
request_data.scope_set.add(ParseProject(project))
if args.preview_images:
for project in constants.PREVIEW_IMAGE_PROJECTS:
request_data.scope_set.add(ParseProject(project))
if support_image_zone_flag and args.image_zone:
list_implementation = lister.MultiScopeLister(
client,
global_service=client.apitools_client.images,
image_zone_flag=args.image_zone,
)
else:
list_implementation = lister.MultiScopeLister(
client, global_service=client.apitools_client.images
)
images = lister.Invoke(request_data, list_implementation)
return self.AugmentImagesStatus(
holder.resources, self._FilterDeprecated(args, images)
)
def _CheckForDeprecated(self, image):
deprecated = False
deprecate_info = image.get('deprecated')
if deprecate_info is not None:
image_state = deprecate_info.get('state')
if image_state and image_state != 'ACTIVE':
deprecated = True
return deprecated
def _FilterDeprecated(self, args, images):
for image in images:
if not self._CheckForDeprecated(image) or args.show_deprecated:
yield image
def AugmentImagesStatus(self, resources, images):
"""Modify images status if necessary, can be overridden."""
del resources # Unused in AugmentImagesStatus
return images
@base.ReleaseTracks(base.ReleaseTrack.BETA)
class ListBeta(List):
"""List Compute Engine images for BETA."""
@classmethod
def Args(cls, parser):
_Args(parser, support_image_zone_flag=True)
def Run(self, args):
return self._Run(args, support_image_zone_flag=True)
def AugmentImagesStatus(self, resources, images):
"""Modify images status based on OrgPolicy."""
return policy.AugmentImagesStatus(
resources, properties.VALUES.core.project.GetOrFail(), images
)
@base.ReleaseTracks(base.ReleaseTrack.ALPHA)
class ListAlpha(List):
"""List Compute Engine images for ALPHA."""
@classmethod
def Args(cls, parser):
_Args(parser, support_image_zone_flag=True)
def Run(self, args):
return self._Run(args, support_image_zone_flag=True)
def AugmentImagesStatus(self, resources, images):
"""Modify images status based on OrgPolicy."""
return policy.AugmentImagesStatus(
resources, properties.VALUES.core.project.GetOrFail(), images
)
List.detailed_help = base_classes.GetGlobalListerHelp('images')

View File

@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*- #
# Copyright 2018 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.
"""Commands for reading and manipulating images."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
@base.ReleaseTracks(base.ReleaseTrack.ALPHA)
class Packages(base.Group):
"""List and diff image packages."""

View File

@@ -0,0 +1,128 @@
# -*- coding: utf-8 -*- #
# Copyright 2018 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 diffing image packages."""
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.containeranalysis import util as containeranalysis_util
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.compute.images.packages import exceptions
from googlecloudsdk.command_lib.compute.images.packages import filter_utils
from googlecloudsdk.command_lib.compute.images.packages import flags as package_flags
class Diff(base.ListCommand):
""" Displays the version differences of packages between two images."""
@staticmethod
def Args(parser):
parser.display_info.AddFormat("""\
table(
name:label=PACKAGE,
version_base:label=VERSION_BASE,
revision_base:label=REVISION_BASE,
version_diff:label=VERSION_DIFF,
revision_diff:label=REVISION_DIFF
)""")
# Add resource flags.
package_flags.AddResourceArgs(parser)
# Add filter flags.
package_flags.AddShowAddedPackagesFlag(parser)
package_flags.AddShowRemovedPackagesFlag(parser)
package_flags.AddShowUpdatedPackagesFlag(parser)
Diff._parser = parser
def _GetVersions(self, image_packages, image_name):
package_versions = {}
for occurrence in image_packages:
package_name = occurrence.installation.name
versions = []
for location in occurrence.installation.location:
versions.append((location.version.name, location.version.revision))
package_versions[package_name] = versions
if not package_versions:
raise exceptions.ImagePackagesInfoUnavailableException(image_name)
return package_versions
def _GetDiff(self, args, package_versions_base, package_versions_diff):
all_package_names = set(package_versions_base.keys()).union(
set(package_versions_diff.keys()))
show_all_diff_packages = True
if (args.show_added_packages or args.show_removed_packages or
args.show_updated_packages):
show_all_diff_packages = False
diff = []
empty = ('-', '-')
for package_name in all_package_names:
versions_base = package_versions_base.get(package_name, [])
versions_diff = package_versions_diff.get(package_name, [])
if set(versions_base) != set(versions_diff):
len_base = len(versions_base)
len_diff = len(versions_diff)
if (show_all_diff_packages or
(args.show_added_packages and len_base == 0 and len_diff != 0) or
(args.show_removed_packages and len_base != 0 and len_diff == 0) or
(args.show_updated_packages and len_base != 0 and len_diff != 0)):
for idx in range(max(len_base, len_diff)):
version_base, revision_base = versions_base[idx] if (
idx < len_base) else empty
version_diff, revision_diff = versions_diff[idx] if (
idx < len_diff) else empty
package_diff = {
'name': package_name,
'version_base': version_base,
'revision_base': revision_base,
'version_diff': version_diff,
'revision_diff': revision_diff
}
diff.append(package_diff)
return sorted(diff, key=lambda package_diff: package_diff['name'])
def Run(self, args):
"""Yields the differences of packages between two images."""
# If not specified, both base project and diff project are the user project.
base_image_ref = args.CONCEPTS.base_image.Parse()
diff_image_ref = args.CONCEPTS.diff_image.Parse()
# Use GA to construct the compute API holder since the containeranalysis
# API always call compute v1 API to refer the compute resources.
holder = base_classes.ComputeApiHolder(base.ReleaseTrack.GA)
resource_filter_base = filter_utils.GetFilter(base_image_ref, holder)
resource_filter_diff = filter_utils.GetFilter(diff_image_ref, holder)
image_packages_base = containeranalysis_util.MakeOccurrenceRequest(
project_id=base_image_ref.project, resource_filter=resource_filter_base,
occurrence_filter=None, resource_urls=None)
image_packages_diff = containeranalysis_util.MakeOccurrenceRequest(
project_id=diff_image_ref.project, resource_filter=resource_filter_diff,
occurrence_filter=None, resource_urls=None)
package_versions_base = self._GetVersions(image_packages_base,
args.base_image)
package_versions_diff = self._GetVersions(image_packages_diff,
args.diff_image)
return self._GetDiff(args, package_versions_base, package_versions_diff)

View File

@@ -0,0 +1,75 @@
# -*- coding: utf-8 -*- #
# Copyright 2018 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 listing 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.containeranalysis import util as containeranalysis_util
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.compute.images.packages import exceptions
from googlecloudsdk.command_lib.compute.images.packages import filter_utils
from googlecloudsdk.command_lib.compute.images.packages import flags as package_flags
from googlecloudsdk.core import properties
class List(base.ListCommand):
"""List the packages in an image.
"""
@staticmethod
def Args(parser):
parser.display_info.AddFormat("""\
table(
name:label=PACKAGE,
version:label=VERSION,
revision:label=REVISION
)""")
# Add resource flag for image.
package_flags.AddImageResourceArg(parser)
def _GetPackageVersions(self, image_packages, image_name):
package_versions = []
for occurrence in image_packages:
package_name = occurrence.installation.name
for location in occurrence.installation.location:
package_version = {'name': package_name,
'version': location.version.name,
'revision': location.version.revision}
package_versions.append(package_version)
if not package_versions:
raise exceptions.ImagePackagesInfoUnavailableException(image_name)
return sorted(package_versions,
key=lambda package_version: package_version['name'])
def Run(self, args):
"""Yields filtered packages."""
project = properties.VALUES.core.project.Get()
image_ref = args.CONCEPTS.image.Parse()
# Use GA to construct the compute API holder since the containeranalysis
# API always call compute v1 API to refer the compute resources.
holder = base_classes.ComputeApiHolder(base.ReleaseTrack.GA)
resource_filter = filter_utils.GetFilter(image_ref, holder)
image_packages = containeranalysis_util.MakeOccurrenceRequest(
project_id=project, resource_filter=resource_filter,
occurrence_filter=None, resource_urls=None)
return self._GetPackageVersions(image_packages, args.image)

View File

@@ -0,0 +1,43 @@
release_tracks: [ALPHA, BETA, GA]
help_text:
brief: Remove IAM policy binding from a Compute Engine image.
description: |
Remove an IAM policy binding from the IAM policy of a Compute Engine image. One binding consists of a member,
a role, and an optional condition.
examples: |
To remove an IAM policy binding for the role of 'roles/compute.securityAdmin' for the user 'test-user@gmail.com'
with image 'my-image', run:
$ {command} my-image --member='user:test-user@gmail.com' --role='roles/compute.securityAdmin'
To remove an IAM policy binding which expires at the end of the year 2018 for the role of
'roles/compute.securityAdmin' and the user 'test-user@gmail.com' with image 'my-image', run:
$ {command} my-image --member='user:test-user@gmail.com' --role='roles/compute.securityAdmin' --condition='expression=request.time < timestamp("2019-01-01T00:00:00Z"),title=expires_end_of_2018,description=Expires at midnight on 2018-12-31'
See https://cloud.google.com/iam/docs/managing-policies for details of
policy role and member types.
request:
collection: compute.images
use_relative_name: false
api_version: v1
BETA:
api_version: beta
ALPHA:
api_version: alpha
arguments:
resource:
help_text: The image for which to remove IAM policy binding from.
spec: !REF googlecloudsdk.command_lib.compute.resources:image
iam:
set_iam_policy_request_path: globalSetPolicyRequest
message_type_overrides:
policy: Policy
set_iam_policy_request: ComputeImagesSetIamPolicyRequest
enable_condition: true
policy_version: 3
get_iam_policy_version_path: optionsRequestedPolicyVersion

View File

@@ -0,0 +1,89 @@
# -*- 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 adding labels to 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.operations import poller
from googlecloudsdk.api_lib.util import waiter
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.compute import labels_doc_helper
from googlecloudsdk.command_lib.compute import labels_flags
from googlecloudsdk.command_lib.compute.images import flags as images_flags
from googlecloudsdk.command_lib.util.args import labels_util
@base.ReleaseTracks(
base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA, base.ReleaseTrack.GA)
class ImagesRemoveLabels(base.UpdateCommand):
DISK_IMAGE_ARG = None
@classmethod
def Args(cls, parser):
cls.DISK_IMAGE_ARG = images_flags.MakeDiskImageArg(plural=False)
cls.DISK_IMAGE_ARG.AddArgument(parser)
labels_flags.AddArgsForRemoveLabels(parser)
def Run(self, args):
holder = base_classes.ComputeApiHolder(self.ReleaseTrack())
client = holder.client.apitools_client
messages = holder.client.messages
image_ref = self.DISK_IMAGE_ARG.ResolveAsResource(
args, holder.resources)
remove_labels = labels_util.GetUpdateLabelsDictFromArgs(args)
image = client.images.Get(
messages.ComputeImagesGetRequest(**image_ref.AsDict()))
if args.all:
# removing all existing labels from the image.
remove_labels = {}
if image.labels:
for label in image.labels.additionalProperties:
remove_labels[label.key] = label.value
labels_update = labels_util.Diff(subtractions=remove_labels).Apply(
messages.GlobalSetLabelsRequest.LabelsValue,
image.labels)
if not labels_update.needs_update:
return image
request = messages.ComputeImagesSetLabelsRequest(
project=image_ref.project,
resource=image_ref.image,
globalSetLabelsRequest=
messages.GlobalSetLabelsRequest(
labelFingerprint=image.labelFingerprint,
labels=labels_update.labels))
operation = client.images.SetLabels(request)
operation_ref = holder.resources.Parse(
operation.selfLink, collection='compute.globalOperations')
operation_poller = poller.Poller(client.images)
return waiter.WaitFor(
operation_poller, operation_ref,
'Updating labels of image [{0}]'.format(
image_ref.Name()))
ImagesRemoveLabels.detailed_help = (
labels_doc_helper.GenerateDetailedHelpForRemoveLabels('image'))

View File

@@ -0,0 +1,41 @@
release_tracks: [ALPHA, BETA, GA]
help_text:
brief: Set the IAM policy for a Compute Engine image.
description: |
Sets the IAM policy for the given image as defined in a JSON or YAML file.
examples: |
The following command will read an IAM policy defined in a JSON file
'policy.json' and set it for the image `my-image`:
$ {command} my-image policy.json
See https://cloud.google.com/iam/docs/managing-policies for details of the
policy file format and contents.
request:
collection: compute.images
use_relative_name: false
modify_request_hooks:
- googlecloudsdk.command_lib.iam.hooks:UseMaxRequestedPolicyVersion:api_field=globalSetPolicyRequest.policy.version
api_version: v1
BETA:
api_version: beta
ALPHA:
api_version: alpha
iam:
set_iam_policy_request_path: globalSetPolicyRequest
message_type_overrides:
policy: Policy
set_iam_policy_request: ComputeImagesSetIamPolicyRequest
ALPHA:
enable_condition: true
BETA:
enable_condition: true
arguments:
resource:
help_text: The image to set IAM policy for.
spec: !REF googlecloudsdk.command_lib.compute.resources:image

View File

@@ -0,0 +1,218 @@
# -*- 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 labels update to 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.calliope import arg_parsers
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.compute import flags
from googlecloudsdk.command_lib.compute.images import flags as images_flags
from googlecloudsdk.command_lib.util.args import labels_util
from googlecloudsdk.core import exceptions
from googlecloudsdk.core import log
DETAILED_HELP = {
'DESCRIPTION':
'*{command}* updates labels for a Compute Engine image.',
'EXAMPLES':
"""\
To update labels ``k0'' and ``k1'' and remove labels with key ``k3'', run:
$ {command} example-image --update-labels=k0=value1,k1=value2 --remove-labels=k3
k0 and k1 will be added as new labels if not already present.
Labels can be used to identify the image and to filter them like:
$ {parent_command} list --filter='labels.k1:value2'
To list only the labels when describing a resource, use --format:
$ {parent_command} describe example-image --format="default(labels)"
""",
}
def _CommonArgs(messages, cls, parser, support_user_licenses=False):
"""Add arguments used for parsing in all command tracks."""
cls.DISK_IMAGE_ARG = images_flags.MakeDiskImageArg(plural=False)
cls.DISK_IMAGE_ARG.AddArgument(parser, operation_type='update')
labels_util.AddUpdateLabelsFlags(parser)
parser.add_argument(
'--description',
help=('An optional text description for the image.'))
parser.add_argument(
'--family',
help=('Name of the image family to use. If an image family is '
'specified when you create an instance or disk, the latest '
'non-deprecated image in the family is used.')
)
architecture_enum_type = messages.Image.ArchitectureValueValuesEnum
excluded_enums = [architecture_enum_type.ARCHITECTURE_UNSPECIFIED.name]
architecture_choices = sorted(
[e for e in architecture_enum_type.names() if e not in excluded_enums])
parser.add_argument(
'--architecture',
choices=architecture_choices,
help=(
'Specifies the architecture or processor type that this image can support. For available processor types on Compute Engine, see https://cloud.google.com/compute/docs/cpu-platforms.'
))
if support_user_licenses:
scope = parser.add_mutually_exclusive_group()
scope.add_argument(
'--update-user-licenses',
type=arg_parsers.ArgList(),
metavar='LICENSE',
action=arg_parsers.UpdateAction,
help=(
'List of user licenses to be updated on an image. These user '
'licenses replace all existing user licenses. If this flag is not '
'provided, all existing user licenses remain unchanged.'))
scope.add_argument(
'--clear-user-licenses',
action='store_true',
help='Remove all existing user licenses on an image.')
@base.ReleaseTracks(base.ReleaseTrack.GA)
class Update(base.UpdateCommand):
"""Update a Compute Engine image."""
DISK_IMAGE_ARG = None
detailed_help = DETAILED_HELP
@classmethod
def Args(cls, parser):
messages = cls._GetApiHolder(no_http=True).client.messages
_CommonArgs(messages, cls, parser, support_user_licenses=False)
def Run(self, args):
return self._Run(args, support_user_licenses=False)
def _Run(self, args, support_user_licenses=True):
holder = base_classes.ComputeApiHolder(self.ReleaseTrack())
client = holder.client
messages = holder.client.messages
image_ref = self.DISK_IMAGE_ARG.ResolveAsResource(
args, holder.resources,
scope_lister=flags.GetDefaultScopeLister(client))
requests = []
result = None
# check if need to update labels
labels_diff = labels_util.Diff.FromUpdateArgs(args)
if labels_diff.MayHaveUpdates():
image = holder.client.apitools_client.images.Get(
messages.ComputeImagesGetRequest(**image_ref.AsDict()))
labels_update = labels_diff.Apply(
messages.GlobalSetLabelsRequest.LabelsValue, image.labels)
if labels_update.needs_update:
request = messages.ComputeImagesSetLabelsRequest(
project=image_ref.project,
resource=image_ref.image,
globalSetLabelsRequest=
messages.GlobalSetLabelsRequest(
labelFingerprint=image.labelFingerprint,
labels=labels_update.labels))
requests.append((client.apitools_client.images, 'SetLabels', request))
should_patch = False
image_resource = messages.Image()
if args.IsSpecified('family'):
image_resource.family = args.family
should_patch = True
if args.IsSpecified('description'):
image_resource.description = args.description
should_patch = True
if args.IsSpecified('architecture'):
image_resource.architecture = messages.Image.ArchitectureValueValuesEnum(
args.architecture)
should_patch = True
if support_user_licenses and (args.IsSpecified('update_user_licenses') or
args.IsSpecified('clear_user_licenses')):
if args.IsSpecified('update_user_licenses'):
image_resource.userLicenses = args.update_user_licenses
else:
image_resource.userLicenses = []
should_patch = True
if should_patch:
request = messages.ComputeImagesPatchRequest(
project=image_ref.project,
imageResource=image_resource,
image=image_ref.Name())
requests.append((client.apitools_client.images, 'Patch', request))
errors_to_collect = []
result = client.AsyncRequests(requests, errors_to_collect)
if errors_to_collect:
raise exceptions.MultiError(errors_to_collect)
if result:
log.status.Print('Updated [{0}].'.format(image_ref))
return result
@classmethod
def _GetApiHolder(cls, no_http=False):
return base_classes.ComputeApiHolder(cls.ReleaseTrack(), no_http)
@base.ReleaseTracks(base.ReleaseTrack.BETA)
class UpdateBeta(Update):
"""Update a Compute Engine image."""
DISK_IMAGE_ARG = None
detailed_help = DETAILED_HELP
@classmethod
def Args(cls, parser):
messages = cls._GetApiHolder(no_http=True).client.messages
_CommonArgs(messages, cls, parser, support_user_licenses=True)
def Run(self, args, support_update_architecture=False):
return self._Run(args)
@base.ReleaseTracks(base.ReleaseTrack.ALPHA)
class UpdateAlpha(UpdateBeta):
"""Update a Compute Engine image."""
DISK_IMAGE_ARG = None
detailed_help = DETAILED_HELP
@classmethod
def Args(cls, parser):
messages = cls._GetApiHolder(no_http=True).client.messages
_CommonArgs(messages, cls, parser, support_user_licenses=True)
def Run(self, args, support_update_architecture=True):
return self._Run(args)

View File

@@ -0,0 +1,31 @@
# -*- 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.
"""Commands for reading and manipulating images."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.calliope import base
@base.ReleaseTracks(base.ReleaseTrack.ALPHA)
class Vulnerabilities(base.Group):
"""List and describe image vulnerabilities and related notes."""
Vulnerabilities.detailed_help = {
'brief': 'List and describe image vulnerabilities and related notes',
}

View File

@@ -0,0 +1,47 @@
# -*- 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 describing images."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
from googlecloudsdk.api_lib.containeranalysis import util as containeranalysis_util
from googlecloudsdk.calliope import base
from googlecloudsdk.core import properties
class DescribeNote(base.DescribeCommand):
"""Describe a Google note."""
@staticmethod
def Args(parser):
parser.add_argument(
'note_name',
help='Name, relative name, or URL of the note.',
)
def Run(self, args):
return containeranalysis_util.MakeGetNoteRequest(
args.note_name, properties.VALUES.core.project.Get(required=True))
DescribeNote.detailed_help = {
'brief': 'Describe a Compute Engine image',
'DESCRIPTION': """
*{command}* displays all data associated with a Compute Engine
image in a project.
""",
}

View File

@@ -0,0 +1,90 @@
# -*- 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 listing 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 lister
from googlecloudsdk.api_lib.containeranalysis import util as containeranalysis_util
from googlecloudsdk.calliope import base
from googlecloudsdk.command_lib.compute import flags as compute_flags
from googlecloudsdk.command_lib.compute.images import flags as image_flags
from googlecloudsdk.core import properties
class List(base.ListCommand):
"""List Google occurrences of PACKAGE_VULNERABILITY.
Lists occurrences with the "kind" field set to "PACKAGE_VULNERABILITY".
The default value of the `--filter` flag for this command is:
vulnerabilityDetails.packageIssue.fixedLocation.version.kind != "MAXIMUM"
so that only vulnerabilities with a known fix are shown. Passing `--filter`
will override this so *all* PACKAGE_VULNERABILITY occurrences are shown, with
any additional provided filters applied.
"""
@staticmethod
def Args(parser):
lister.AddBaseListerArgs(parser)
parser.display_info.AddFormat("""\
table(
name,
noteName.basename():label=NOTE,
vulnerabilityDetails.severity,
vulnerabilityDetails.cvssScore,
vulnerabilityDetails.packageIssue.affectedLocation.package.join(','):label=PACKAGES
)""")
List._image_arg = image_flags.MakeDiskImageArg(
required=False, name='--image')
List._image_arg.AddArgument(parser, operation_type='create')
# This allows the occurrence if ANY of its PackageIssues have
# fixedLocation.version.kind != MAXIMUM.
# TODO(b/72375279): Do server-side
parser.display_info.AddFilter(
'vulnerabilityDetails.packageIssue.fixedLocation.version.kind != '
'"MAXIMUM"')
def _GetFilter(self, args, holder):
filters = [
'kind = "PACKAGE_VULNERABILITY"', # Display only vulnerabilities
# Display only compute metadata
'has_prefix(resource_url,"https://compute.googleapis.com/compute/")',
]
if args.image:
image_ref = self._image_arg.ResolveAsResource(
args,
holder.resources,
scope_lister=compute_flags.GetDefaultScopeLister(
holder.client))
image_url = image_ref.SelfLink()
filters.append('has_prefix(resource_url, "{}")'.format(image_url))
return ' AND '.join(filters)
def Run(self, args):
"""Yields filtered vulnerabilities."""
project = properties.VALUES.core.project.Get()
holder = base_classes.ComputeApiHolder(base.ReleaseTrack.GA)
resource_filter = self._GetFilter(args, holder)
return containeranalysis_util.MakeOccurrenceRequest(
project_id=project, resource_filter=resource_filter,
occurrence_filter=None, resource_urls=None)