File: //snap/google-cloud-cli/current/lib/surface/compute/instances/os_inventory/list_instances.py
# -*- coding: utf-8 -*- #
# Copyright 2019 Google LLC. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Command for listing instances with specific OS inventory data values."""
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
import base64
import json
import os
import zlib
from googlecloudsdk.api_lib.compute import base_classes
from googlecloudsdk.api_lib.compute import lister
from googlecloudsdk.api_lib.compute import utils
from googlecloudsdk.calliope import base
from googlecloudsdk.calliope import exceptions
from googlecloudsdk.command_lib.compute import completers
from googlecloudsdk.command_lib.compute.instances import flags
from googlecloudsdk.core import properties
from googlecloudsdk.core.resource import resource_filter
from googlecloudsdk.core.resource import resource_projector
@base.ReleaseTracks(base.ReleaseTrack.GA, base.ReleaseTrack.BETA)
class ListInstances(base.ListCommand):
r"""List instances with specific OS inventory data values.
{command} displays all Compute Engine instances in a project matching
an inventory filter. Run $ gcloud topic filters to see the supported filter
syntax.
## EXAMPLES
To list all instances with OS inventory data in a project in table form, run:
$ {command}
To list the URIs of all instances whose OS short name contains rhel, run:
$ {command} --inventory-filter="ShortName:rhel" --uri
To list the URIs of all instances whose OS short name is equal to rhel, run:
$ {command} --os-shortname="rhel" --uri
To list all instances with package google-cloud-sdk of version 235.0.0-0
installed, run:
$ {command} --package-name="google-cloud-sdk" \
--package-version="235.0.0-0"
To list all instances with package name matching a regex ^google-cloud*
available for update through apt, run:
$ {command} --inventory-filter="\
PackageUpdates.apt[].Name~^google-cloud*"
To list all instances with package update google-cloud-sdk of version greater
than or equal to 235.0.0-0 available through apt, run:
$ {command} --inventory-filter="\
PackageUpdates.apt[].['google-cloud-sdk'].Version>=235.0.0-0"
To list all instances missing the Stackdriver monitoring package
stackdriver-agent, run:
$ {command} --inventory-filter="\
NOT(InstalledPackages:stackdriver-agent)"
To list all Windows instances with an installed qfe hotfix whose ID equals
KB4462930, run:
$ {command} --inventory-filter="\
InstalledPackages.qfe[].HotFixID=KB4462930"
To list all Windows instances with a wua update whose description contains the
word Security, run:
$ {command} --inventory-filter="\
InstalledPackages.wua[].Description:Security"
"""
_GUEST_ATTRIBUTES_PACKAGE_FIELD_KEYS = ('InstalledPackages', 'PackageUpdates')
_SPECIAL_PACKAGE_MANAGERS = ('wua', 'qfe', 'zypperPatches')
_REGULAR_PACKAGE_MANAGERS = ('cos', 'deb', 'googet', 'rpm', 'gem', 'pip')
@staticmethod
def Args(parser):
parser.display_info.AddFormat(flags.DEFAULT_LIST_FORMAT)
parser.display_info.AddUriFunc(utils.MakeGetUriFunc())
parser.display_info.AddCacheUpdater(completers.InstancesCompleter)
parser.add_argument(
'--inventory-filter',
type=str,
help="""Filter expression for matching against OS inventory criteria""")
filter_group = parser.add_group(
help='Exact match values for OS inventory data:')
filter_group.add_argument(
'--os-shortname',
type=str,
help="""If specified, only instances with this OS shortname in their
inventory data will be displayed.""")
filter_group.add_argument(
'--os-version',
type=str,
help="""If specified, only instances with this OS version in their
inventory data will be displayed.""")
filter_group.add_argument(
'--kernel-version',
type=str,
help="""If specified, only instances with this kernel version in their
inventory data will be displayed.""")
filter_group.add_argument(
'--package-name',
type=str,
help="""If specified, only instances with an installed package of this
name in their inventory data will be displayed.""")
filter_group.add_argument(
'--package-version',
type=str,
help="""If specified with a package name, only instances with the
installed package of this version in their inventory data will be
displayed.""")
def _GetGuestAttributesRequest(self, messages, instance_name, project, zone):
return messages.ComputeInstancesGetGuestAttributesRequest(
instance=instance_name,
project=project,
queryPath='guestInventory/',
zone=zone)
def _GetAllGuestInventoryGuestAttributes(self, holder, instances):
client = holder.client
messages = client.messages
project = properties.VALUES.core.project.GetOrFail()
requests = [
self._GetGuestAttributesRequest(messages, instance['name'], project,
os.path.basename(instance['zone']))
for instance in instances
]
responses = holder.client.AsyncRequests(
[
(client.apitools_client.instances, 'GetGuestAttributes', request)
for request in requests
]
)
for response in filter(None, responses):
for item in response.queryValue.items:
if item.key in self._GUEST_ATTRIBUTES_PACKAGE_FIELD_KEYS:
item.value = zlib.decompress(
base64.b64decode(item.value), zlib.MAX_WBITS | 32)
return responses
def _GetFormattedGuestAttributes(self, guest_attributes):
guest_attributes_json = resource_projector.MakeSerializable(
guest_attributes)
formatted_guest_attributes = {}
for guest_attribute in guest_attributes_json:
guest_attribute_key = guest_attribute['key']
# Only reformat the guest attribute value
# for certain fields that contain JSON data.
if guest_attribute_key in self._GUEST_ATTRIBUTES_PACKAGE_FIELD_KEYS:
formatted_packages_info = {}
guest_attribute_json = json.loads(guest_attribute['value'])
for package_manager, package_list in guest_attribute_json.items():
if package_manager in self._SPECIAL_PACKAGE_MANAGERS:
# No reformatting for special package managers.
formatted_packages_info[package_manager] = package_list
else:
# Reformat package info published by standard package managers
formatted_packages_list = []
for package in package_list:
name = package['Name']
info = {'Arch': package['Arch'], 'Version': package['Version']}
formatted_packages_list.append({'Name': name, name: info})
formatted_packages_info[package_manager] = formatted_packages_list
guest_attribute['value'] = formatted_packages_info
formatted_guest_attributes[guest_attribute_key] = guest_attribute['value']
return json.loads(json.dumps(formatted_guest_attributes))
def _GetInventoryFilteredInstances(self, instances, responses, query):
filtered_instances = []
for instance, response in zip(instances, responses):
# No listing instances without inventory data.
if instance is not None and response is not None:
guest_attributes = response.queryValue.items
formatted_guest_attributes_json = self._GetFormattedGuestAttributes(
guest_attributes)
if query.Evaluate(formatted_guest_attributes_json):
filtered_instances.append(instance)
return filtered_instances
def _GetInventoryFilterQuery(self, args):
query_list = []
def _AppendQuery(query):
query_list.append('({})'.format(query))
if args.inventory_filter:
_AppendQuery(args.inventory_filter)
if args.os_shortname:
_AppendQuery('ShortName=' + args.os_shortname)
if args.os_version:
_AppendQuery('Version=' + args.os_version)
if args.kernel_version:
_AppendQuery('KernelVersion=' + args.kernel_version)
installed_packages_query_prefixes = [
'InstalledPackages.' + package_manager + '[].'
for package_manager in self._REGULAR_PACKAGE_MANAGERS
]
if args.package_version:
if not args.package_name:
raise exceptions.InvalidArgumentException(
'--package-version',
'package version must be specified together with a package name. '
'e.g. --package-name google-cloud-sdk --package-version 235.0.0-0')
else:
package_name = '[\'{}\']'.format(args.package_name)
_AppendQuery(' OR '.join([
'({})'.format(prefix + package_name + '.Version=' +
args.package_version)
for prefix in installed_packages_query_prefixes
]))
else:
if args.package_name:
_AppendQuery(' OR '.join([
'({})'.format(prefix + 'Name=' + args.package_name)
for prefix in installed_packages_query_prefixes
]))
return ' AND '.join(query_list)
def Run(self, args):
query = resource_filter.Compile(self._GetInventoryFilterQuery(args))
holder = base_classes.ComputeApiHolder(self.ReleaseTrack())
client = holder.client
request_data = lister.ParseMultiScopeFlags(args, holder.resources)
list_implementation = lister.MultiScopeLister(
client,
zonal_service=client.apitools_client.instances,
aggregation_service=client.apitools_client.instances)
instances_iterator = lister.Invoke(request_data, list_implementation)
instances = list(instances_iterator)
responses = self._GetAllGuestInventoryGuestAttributes(holder, instances)
return self._GetInventoryFilteredInstances(instances, responses, query)
@base.ReleaseTracks(base.ReleaseTrack.ALPHA)
class ListInstancesAlpha(ListInstances):
r"""List instances with specific OS inventory data values.
{command} displays all Google Compute Engine instances in a project matching
an inventory filter. Run $ gcloud topic filters to see the supported filter
syntax.
## EXAMPLES
To list all instances with OS inventory data in a project in table form, run:
$ {command}
To list the URIs of all instances whose OS short name contains rhel, run:
$ {command} --inventory-filter="ShortName:rhel" --uri
To list the URIs of all instances whose OS short name is equal to rhel, run:
$ {command} --os-shortname="rhel" --uri
To list all instances with package google-cloud-sdk of version 235.0.0-0
installed, run:
$ {command} --package-name="google-cloud-sdk" \
--package-version="235.0.0-0"
To list all instances with package name matching a regex ^google-cloud*
available for update through apt, run:
$ {command} --inventory-filter="\
PackageUpdates.apt[].Name~^google-cloud*"
To list all instances with package update google-cloud-sdk of version greater
than or equal to 235.0.0-0 available through apt, run:
$ {command} --inventory-filter="\
PackageUpdates.apt[].['google-cloud-sdk'].Version>=235.0.0-0"
To list all instances missing the Stackdriver monitoring package
stackdriver-agent, run:
$ {command} --inventory-filter="\
NOT(InstalledPackages:stackdriver-agent)"
To list all Windows instances with an installed qfe hotfix whose ID equals
KB4462930, run:
$ {command} --inventory-filter="\
InstalledPackages.qfe[].HotFixID=KB4462930"
To list all Windows instances with a wua update whose description contains the
word Security, run:
$ {command} --inventory-filter="\
InstalledPackages.wua[].Description:Security"
"""