398 lines
13 KiB
Python
398 lines
13 KiB
Python
# -*- coding: utf-8 -*- #
|
|
# Copyright 2020 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.
|
|
"""recommender API insights list command."""
|
|
|
|
from __future__ import absolute_import
|
|
from __future__ import division
|
|
from __future__ import unicode_literals
|
|
|
|
import itertools
|
|
|
|
from apitools.base.py import exceptions as apitools_exceptions
|
|
from googlecloudsdk.api_lib.asset import client_util
|
|
from googlecloudsdk.api_lib.recommender import insight
|
|
from googlecloudsdk.api_lib.recommender import insight_types
|
|
from googlecloudsdk.api_lib.recommender import locations
|
|
from googlecloudsdk.calliope import arg_parsers
|
|
from googlecloudsdk.calliope import base
|
|
from googlecloudsdk.command_lib.recommender import flags
|
|
from googlecloudsdk.command_lib.run import exceptions
|
|
from googlecloudsdk.core import log
|
|
|
|
|
|
DETAILED_HELP = {
|
|
'EXAMPLES': """
|
|
To list all insights for a billing account:
|
|
|
|
$ {command} --project=project-id --location=global --insight-type=google.compute.firewall.Insight
|
|
""",
|
|
}
|
|
|
|
DISPLAY_FORMAT = """
|
|
table(
|
|
name.basename(): label=INSIGHT_ID,
|
|
category: label=CATEGORY,
|
|
stateInfo.state: label=INSIGHT_STATE,
|
|
lastRefreshTime: label=LAST_REFRESH_TIME,
|
|
severity: label=SEVERITY,
|
|
insightSubtype: label=INSIGHT_SUBTYPE,
|
|
description: label=DESCRIPTION
|
|
)
|
|
"""
|
|
|
|
|
|
@base.DefaultUniverseOnly
|
|
@base.ReleaseTracks(base.ReleaseTrack.ALPHA, base.ReleaseTrack.BETA)
|
|
class List(base.ListCommand):
|
|
r"""List insights for Google Cloud resources.
|
|
|
|
This command lists all insights for the specified Google Cloud resource,
|
|
location, and insight type. If an insight type or location is not specified,
|
|
insights for all supported insight types or locations, respectively, are
|
|
listed. If the `--recursive` flag is set, insights for child resources and
|
|
projects are also listed. Supported insight types can be found here:
|
|
https://cloud.google.com/recommender/docs/insights/insight-types.
|
|
"""
|
|
|
|
detailed_help = DETAILED_HELP
|
|
|
|
@staticmethod
|
|
def Args(parser):
|
|
"""Args is called by calliope to gather arguments for this command.
|
|
|
|
Args:
|
|
parser: An argparse parser that you can use to add arguments that go on
|
|
the command line after this command.
|
|
"""
|
|
flags.AddParentFlagsToParser(parser)
|
|
parser.add_argument(
|
|
'--location',
|
|
metavar='LOCATION',
|
|
required=False,
|
|
help=(
|
|
'Location to list insights for. If no location is specified,'
|
|
' insights for all supported locations are listed.'
|
|
' Not specifying a location can add 15-20 seconds to the runtime.'
|
|
),
|
|
)
|
|
parser.add_argument(
|
|
'--insight-type',
|
|
metavar='INSIGHT_TYPE',
|
|
required=False,
|
|
help=(
|
|
'Insight type to list insights for. If no insight type is'
|
|
' specified, insights for all supported insight types are listed.'
|
|
' Supported insight types can be found here: '
|
|
'https://cloud.google.com/recommender/docs/insights/insight-types'
|
|
' Not specifying an insight-type can add a minute to the runtime.'
|
|
),
|
|
)
|
|
parser.add_argument(
|
|
'--recursive',
|
|
action=arg_parsers.StoreTrueFalseAction,
|
|
required=False,
|
|
help=("""
|
|
In addition to listing the insights for the specified organization
|
|
or folder, recursively list all of
|
|
the insights for the resource's child resources, including
|
|
their descendants (for example, a folder's sub-folders), and for
|
|
the resource's child projects. For example, when using the
|
|
`--recursive` flag and specifying an organization, the response
|
|
lists all of the insights associated with that
|
|
organization, all of the insights associated with that
|
|
organization's folders and sub-folders, and all of the
|
|
insights associated with that organization's child
|
|
projects. The maximum number of resources (organization,
|
|
folders, projects, and descendant resources) that can be accessed at
|
|
once with the `--recursive` flag is 100. For a larger
|
|
number of nested resources, use
|
|
[BigQuery export](https://cloud.google.com/recommender/docs/bq-export/export-recommendations-to-bq).
|
|
Using `--recursive` can add 15-20 seconds per resource to the
|
|
runtime.
|
|
"""),
|
|
)
|
|
parser.display_info.AddFormat(DISPLAY_FORMAT)
|
|
|
|
def setArgs(self, args):
|
|
"""Setups up args to search all resources under a project, folder, or organization.
|
|
|
|
Args:
|
|
args: argparse.Namespace, The arguments that this command was invoked
|
|
with.
|
|
|
|
Returns:
|
|
(argparse.Namespace) args with additional parameters setup
|
|
"""
|
|
|
|
args.read_mask = '*'
|
|
args.asset_types = [
|
|
# gcloud-disable-gdu-domain
|
|
'cloudresourcemanager.googleapis.com/Project',
|
|
# gcloud-disable-gdu-domain
|
|
'cloudresourcemanager.googleapis.com/Folder'
|
|
]
|
|
args.order_by = 'createTime'
|
|
args.query = '*'
|
|
if args.project:
|
|
args.scope = 'projects/' + args.project
|
|
if args.organization:
|
|
args.scope = 'organizations/' + args.organization
|
|
if args.folder:
|
|
args.scope = 'folders/' + args.folder
|
|
|
|
return args
|
|
|
|
def read(self, asset_in):
|
|
if isinstance(asset_in, list):
|
|
return asset_in[0]
|
|
else:
|
|
return asset_in
|
|
|
|
def AddResource(self, resource_location) -> bool:
|
|
if resource_location not in self.resource_locations:
|
|
self.resource_locations.append(resource_location)
|
|
return True
|
|
return False
|
|
|
|
def searchAllResources(self, args):
|
|
"""Search all nested resources under a project, folder, or organization.
|
|
|
|
Args:
|
|
args: argparse.Namespace, The arguments that this command was invoked
|
|
with.
|
|
|
|
Returns:
|
|
(List): a list of all Google Cloud resource,location pairs
|
|
"""
|
|
|
|
args = self.setArgs(args)
|
|
client = client_util.AssetSearchClient(client_util.DEFAULT_API_VERSION)
|
|
resources = list(client.SearchAllResources(args))
|
|
self.resource_locations = []
|
|
|
|
for r in resources:
|
|
parent_resource = f'{self.read(args.scope)}/locations/{r.location}'
|
|
if 'project' not in parent_resource:
|
|
self.AddResource(parent_resource)
|
|
|
|
# gcloud-disable-gdu-domain
|
|
if r.assetType == 'cloudresourcemanager.googleapis.com/Project':
|
|
self.AddResource(f'{self.read(r.project)}/locations/{r.location}')
|
|
|
|
# gcloud-disable-gdu-domain
|
|
if (
|
|
r.assetType == 'cloudresourcemanager.googleapis.com/Folder'
|
|
and self.AddResource(f'{self.read(r.folders)}/locations/{r.location}')
|
|
):
|
|
args.scope = self.read(r.folders)
|
|
resources.extend(client.SearchAllResources(args))
|
|
|
|
if len(self.resource_locations) > 100:
|
|
raise exceptions.UnsupportedOperationError(
|
|
'The maximum number of resources (organizations, folders, projects,'
|
|
' and descendant resources) that can be accessed to list'
|
|
' insights is 100. To access'
|
|
' a larger number of resources, use BigQuery Export.'
|
|
)
|
|
|
|
return self.resource_locations
|
|
|
|
def CollectAssets(self, args):
|
|
"""Iterate through search all resources response and collects unique Google Cloud resouce,location pairs.
|
|
|
|
Args:
|
|
args: argparse.Namespace, The arguments that this command was invoked
|
|
with.
|
|
|
|
Returns:
|
|
(List): a list of all Google Cloud resource,location pairs
|
|
"""
|
|
|
|
# Collect Assets and Locations
|
|
log.status.Print('Collecting Resources... This may take some time...')
|
|
if args.recursive:
|
|
resource_locations = self.searchAllResources(args)
|
|
elif args.location is None:
|
|
loc_client = locations.CreateClient(self.ReleaseTrack())
|
|
resource_locations = [
|
|
loc.name
|
|
for loc in loc_client.List(
|
|
args.page_size,
|
|
project=args.project,
|
|
organization=args.organization,
|
|
folder=args.folder,
|
|
billing_account=args.billing_account,
|
|
)
|
|
]
|
|
else:
|
|
resource_locations = [
|
|
flags.GetResourceSegment(args) + f'/locations/{args.location}'
|
|
]
|
|
return resource_locations
|
|
|
|
def ListInsightTypes(self, args):
|
|
"""List all Insight Types.
|
|
|
|
Args:
|
|
args: argparse.Namespace, The arguments that this command was invoked
|
|
with.
|
|
|
|
Returns:
|
|
(list) all insight-types in a list of strings
|
|
"""
|
|
|
|
insighttypes_client = insight_types.CreateClient(self.ReleaseTrack())
|
|
insighttypes_response = insighttypes_client.List(args.page_size)
|
|
return [response.name for response in insighttypes_response]
|
|
|
|
def GetInsights(self, args, asset_insight_types):
|
|
"""Collects all insights for a given Google Cloud Resource.
|
|
|
|
Args:
|
|
args: argparse.Namespace, The arguments that this command was invoked
|
|
with.
|
|
asset_insight_types: list, The list of Google Cloud resource insight types
|
|
URL to collect insights
|
|
|
|
Returns:
|
|
(Insights) a iterator for all returned insights
|
|
"""
|
|
insights = []
|
|
insights_client = insight.CreateClient(self.ReleaseTrack())
|
|
resource_prev = None
|
|
location_prev = None
|
|
for resource, location, insight_type in asset_insight_types:
|
|
if resource != resource_prev or location != location_prev:
|
|
log.status.Print(f'Reading Insights for: {resource} {location}')
|
|
resource_prev = resource
|
|
location_prev = location
|
|
parent_name = '/'.join([resource, location, insight_type])
|
|
new_insights = insights_client.List(
|
|
parent_name, args.page_size
|
|
)
|
|
try: # skip insight types that the user does not have access to.
|
|
peek = next(new_insights) # execute first element of generator
|
|
except (
|
|
apitools_exceptions.HttpBadRequestError,
|
|
apitools_exceptions.BadStatusCodeError,
|
|
StopIteration,
|
|
):
|
|
continue
|
|
insights = itertools.chain(
|
|
insights, (peek,), new_insights
|
|
)
|
|
|
|
return insights
|
|
|
|
def Run(self, args):
|
|
"""Run 'gcloud recommender insights list'.
|
|
|
|
Args:
|
|
args: argparse.Namespace, The arguments that this command was invoked
|
|
with.
|
|
|
|
Returns:
|
|
The list of insights for this Google Cloud Resource.
|
|
"""
|
|
|
|
# Collect Assets and Locations
|
|
resource_locations = self.CollectAssets(args)
|
|
|
|
# collect insights for all insight types
|
|
asset_insight_types = []
|
|
for asset in resource_locations:
|
|
tokens = asset.split('/')
|
|
resource = '/'.join(tokens[:2])
|
|
location = '/'.join(tokens[2:4])
|
|
if args.insight_type is not None:
|
|
asset_insight_types.append(
|
|
(resource, location, f'insightTypes/{args.insight_type}')
|
|
)
|
|
else: # loop through all insight types
|
|
asset_insight_types.extend([
|
|
(resource, location, f'insightTypes/{name}')
|
|
for name in self.ListInsightTypes(args)
|
|
])
|
|
|
|
return self.GetInsights(args, asset_insight_types)
|
|
|
|
|
|
@base.DefaultUniverseOnly
|
|
@base.ReleaseTracks(base.ReleaseTrack.GA)
|
|
class ListOriginal(base.ListCommand):
|
|
r"""List insights for a Google Cloud entity.
|
|
|
|
This command lists all insights for a given Google Cloud entity, location, and
|
|
insight type. Supported insight-types can be found here:
|
|
https://cloud.google.com/recommender/docs/insights/insight-types. Currently
|
|
the following Google Cloud entity types are supported: project,
|
|
billing_account, folder, and organization.
|
|
"""
|
|
|
|
detailed_help = DETAILED_HELP
|
|
|
|
@staticmethod
|
|
def Args(parser):
|
|
"""Args is called by calliope to gather arguments for this command.
|
|
|
|
Args:
|
|
parser: An argparse parser that you can use to add arguments that go on
|
|
the command line after this command.
|
|
"""
|
|
flags.AddParentFlagsToParser(parser)
|
|
parser.add_argument(
|
|
'--location',
|
|
metavar='LOCATION',
|
|
required=True,
|
|
help='Location to list insights for. ',
|
|
)
|
|
parser.add_argument(
|
|
'--insight-type',
|
|
metavar='INSIGHT_TYPE',
|
|
required=True,
|
|
help=(
|
|
'Insight type to list insights for. Supported insight-types can '
|
|
'be found here: '
|
|
'https://cloud.google.com/recommender/docs/insights/insight-types'
|
|
),
|
|
)
|
|
parser.display_info.AddFormat("""
|
|
table(
|
|
name.basename(): label=INSIGHT_ID,
|
|
category: label=CATEGORY,
|
|
stateInfo.state: label=INSIGHT_STATE,
|
|
lastRefreshTime: label=LAST_REFRESH_TIME,
|
|
severity: label=SEVERITY,
|
|
insightSubtype: label=INSIGHT_SUBTYPE,
|
|
description: label=DESCRIPTION
|
|
)
|
|
""")
|
|
|
|
def Run(self, args):
|
|
"""Run 'gcloud recommender insights list'.
|
|
|
|
Args:
|
|
args: argparse.Namespace, The arguments that this command was invoked
|
|
with.
|
|
|
|
Returns:
|
|
The list of insights for this project.
|
|
"""
|
|
|
|
insights_client = insight.CreateClient(self.ReleaseTrack())
|
|
parent_name = flags.GetInsightTypeName(args)
|
|
|
|
return insights_client.List(parent_name, args.page_size)
|