1# -*- coding: utf-8 -*- #
2# Copyright 2018 Google LLC. All Rights Reserved.
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#    http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15"""Shared utilities for access the CloudAsset API client."""
16
17from __future__ import absolute_import
18from __future__ import division
19from __future__ import unicode_literals
20
21from apitools.base.py import encoding
22from apitools.base.py import list_pager
23
24from googlecloudsdk.api_lib.util import apis
25from googlecloudsdk.calliope import exceptions as gcloud_exceptions
26from googlecloudsdk.command_lib.asset import utils as asset_utils
27from googlecloudsdk.command_lib.util.apis import arg_utils
28from googlecloudsdk.command_lib.util.args import repeated
29from googlecloudsdk.core import exceptions as core_exceptions
30from googlecloudsdk.core import log
31from googlecloudsdk.core.util import times
32
33API_NAME = 'cloudasset'
34DEFAULT_API_VERSION = 'v1'
35V1P1BETA1_API_VERSION = 'v1p1beta1'
36V1P4ALPHA1_API_VERSION = 'v1p4alpha1'
37V1P4BETA1_API_VERSION = 'v1p4beta1'
38V1P5BETA1_API_VERSION = 'v1p5beta1'
39V1P7BETA1_API_VERSION = 'v1p7beta1'
40_HEADERS = {
41    'Content-Type': 'application/x-www-form-urlencoded',
42    'X-HTTP-Method-Override': 'GET'
43}
44_HTTP_ERROR_FORMAT = ('HTTP request failed with status code {}. '
45                      'Response content: {}')
46# A dictionary that captures version differences for IAM Policy Analyzer.
47_IAM_POLICY_ANALYZER_VERSION_DICT_JSON = {
48    V1P4ALPHA1_API_VERSION: {
49        'resource_selector': 'resourceSelector',
50        'identity_selector': 'identitySelector',
51        'access_selector': 'accessSelector',
52        'options': 'options',
53    },
54    V1P4BETA1_API_VERSION: {
55        'resource_selector': 'analysisQuery_resourceSelector',
56        'identity_selector': 'analysisQuery_identitySelector',
57        'access_selector': 'analysisQuery_accessSelector',
58        'options': 'options',
59    },
60    DEFAULT_API_VERSION: {
61        'resource_selector': 'analysisQuery_resourceSelector',
62        'identity_selector': 'analysisQuery_identitySelector',
63        'access_selector': 'analysisQuery_accessSelector',
64        'options': 'analysisQuery_options',
65    },
66}
67
68
69class MessageDecodeError(core_exceptions.Error):
70  """Error raised when a failure to decode a message occurs."""
71
72
73def GetMessages(version=DEFAULT_API_VERSION):
74  """Import and return the cloudasset messages module.
75
76  Args:
77    version: the API version
78
79  Returns:
80    cloudasset message module.
81  """
82  return apis.GetMessagesModule(API_NAME, version)
83
84
85def GetClient(version=DEFAULT_API_VERSION):
86  """Import and return the cloudasset client module.
87
88  Args:
89    version: the API version
90
91  Returns:
92    cloudasset API client module.
93  """
94  return apis.GetClientInstance(API_NAME, version)
95
96
97def ContentTypeTranslation(content_type):
98  """Translate content type from gcloud format to API format.
99
100  Args:
101    content_type: the gcloud format of content_type
102
103  Returns:
104    cloudasset API format of content_type.
105  """
106  if content_type == 'resource':
107    return 'RESOURCE'
108  if content_type == 'iam-policy':
109    return 'IAM_POLICY'
110  if content_type == 'org-policy':
111    return 'ORG_POLICY'
112  if content_type == 'access-policy':
113    return 'ACCESS_POLICY'
114  if content_type == 'os-inventory':
115    return 'OS_INVENTORY'
116  if content_type == 'relationship':
117    return 'RELATIONSHIP'
118  return 'CONTENT_TYPE_UNSPECIFIED'
119
120
121def PartitionKeyTranslation(partition_key):
122  if partition_key == 'read-time':
123    return 'READ_TIME'
124  if partition_key == 'request-time':
125    return 'REQUEST_TIME'
126  return 'PARTITION_KEY_UNSPECIFIED'
127
128
129def MakeGetAssetsHistoryHttpRequests(args,
130                                     service,
131                                     api_version=DEFAULT_API_VERSION):
132  """Manually make the get assets history request."""
133  messages = GetMessages(api_version)
134
135  encoding.AddCustomJsonFieldMapping(
136      messages.CloudassetBatchGetAssetsHistoryRequest,
137      'readTimeWindow_startTime', 'readTimeWindow.startTime')
138  encoding.AddCustomJsonFieldMapping(
139      messages.CloudassetBatchGetAssetsHistoryRequest, 'readTimeWindow_endTime',
140      'readTimeWindow.endTime')
141
142  content_type = arg_utils.ChoiceToEnum(
143      args.content_type, messages.CloudassetBatchGetAssetsHistoryRequest
144      .ContentTypeValueValuesEnum)
145  parent = asset_utils.GetParentNameForGetHistory(args.organization,
146                                                  args.project)
147  start_time = times.FormatDateTime(args.start_time)
148  end_time = None
149  if args.IsSpecified('end_time'):
150    end_time = times.FormatDateTime(args.end_time)
151
152  response = service.BatchGetAssetsHistory(
153      messages.CloudassetBatchGetAssetsHistoryRequest(
154          assetNames=args.asset_names,
155          contentType=content_type,
156          parent=parent,
157          readTimeWindow_endTime=end_time,
158          readTimeWindow_startTime=start_time,
159      ))
160
161  for asset in response.assets:
162    yield asset
163
164
165def _RenderAnalysisforAnalyzeIamPolicy(analysis):
166  """Renders the analysis query and results of the AnalyzeIamPolicy request."""
167
168  for analysis_result in analysis.analysisResults:
169    entry = {}
170
171    policy = {
172        'attachedResource': analysis_result.attachedResourceFullName,
173        'binding': analysis_result.iamBinding,
174    }
175    entry['policy'] = policy
176
177    entry['ACLs'] = []
178    for acl in analysis_result.accessControlLists:
179      acls = {}
180      acls['identities'] = analysis_result.identityList.identities
181      acls['accesses'] = acl.accesses
182      acls['resources'] = acl.resources
183      entry['ACLs'].append(acls)
184
185    yield entry
186
187
188def _RenderResponseforAnalyzeIamPolicy(response,
189                                       analyze_service_account_impersonation):
190  """Renders the response of the AnalyzeIamPolicy request."""
191
192  if response.fullyExplored:
193    msg = 'Your analysis request is fully explored. '
194  else:
195    msg = ('Your analysis request is NOT fully explored. You can use the '
196           '--show-response option to see the unexplored part. ')
197
198  has_results = False
199  if response.mainAnalysis.analysisResults:
200    has_results = True
201  if (not has_results) and analyze_service_account_impersonation:
202    for sa_impersonation_analysis in response.serviceAccountImpersonationAnalysis:
203      if sa_impersonation_analysis.analysisResults:
204        has_results = True
205        break
206
207  if not has_results:
208    msg += 'No matching ACL is found.'
209  else:
210    msg += ('The ACLs matching your requests are listed per IAM policy binding'
211            ', so there could be duplications.')
212
213  for entry in _RenderAnalysisforAnalyzeIamPolicy(response.mainAnalysis):
214    yield entry
215
216  if analyze_service_account_impersonation:
217    for analysis in response.serviceAccountImpersonationAnalysis:
218      title = {
219          'Service Account Impersonation Analysis Query': analysis.analysisQuery
220      }
221      yield title
222      for entry in _RenderAnalysisforAnalyzeIamPolicy(analysis):
223        yield entry
224
225  log.status.Print(msg)
226
227
228def MakeAnalyzeIamPolicyHttpRequests(args,
229                                     service,
230                                     messages,
231                                     api_version=V1P4ALPHA1_API_VERSION):
232  """Manually make the analyze IAM policy request."""
233  if api_version == V1P4ALPHA1_API_VERSION:
234    folder = None
235    project = None
236  else:
237    folder = args.folder
238    project = args.project
239
240  parent = asset_utils.GetParentNameForAnalyzeIamPolicy(args.organization,
241                                                        project, folder)
242
243  full_resource_name = args.full_resource_name if args.IsSpecified(
244      'full_resource_name') else None
245
246  identity = args.identity if args.IsSpecified('identity') else None
247
248  roles = args.roles if args.IsSpecified('roles') else []
249
250  permissions = args.permissions if args.IsSpecified('permissions') else []
251
252  expand_groups = args.expand_groups if args.expand_groups else None
253
254  expand_resources = args.expand_resources if args.expand_resources else None
255
256  expand_roles = args.expand_roles if args.expand_roles else None
257
258  output_resource_edges = None
259  if args.output_resource_edges:
260    if (api_version == V1P4BETA1_API_VERSION or
261        api_version == DEFAULT_API_VERSION) and (not args.show_response):
262      raise gcloud_exceptions.InvalidArgumentException(
263          '--output-resource-edges',
264          'Must be set together with --show-response to take effect.')
265    output_resource_edges = args.output_resource_edges
266
267  output_group_edges = None
268  if args.output_group_edges:
269    if (api_version == V1P4BETA1_API_VERSION or
270        api_version == DEFAULT_API_VERSION) and (not args.show_response):
271      raise gcloud_exceptions.InvalidArgumentException(
272          '--output-group-edges',
273          'Must be set together with --show-response to take effect.')
274    output_group_edges = args.output_group_edges
275
276  output_partial_result_before_timeout = None
277  if api_version == V1P4ALPHA1_API_VERSION and args.IsSpecified(
278      'output_partial_result_before_timeout'):
279    output_partial_result_before_timeout = args.output_partial_result_before_timeout
280
281  execution_timeout = None
282  if (api_version == V1P4BETA1_API_VERSION or api_version == DEFAULT_API_VERSION
283     ) and args.IsSpecified('execution_timeout'):
284    execution_timeout = str(args.execution_timeout) + 's'
285
286  analyze_service_account_impersonation = None
287  if api_version == V1P4BETA1_API_VERSION or api_version == DEFAULT_API_VERSION:
288    analyze_service_account_impersonation = args.analyze_service_account_impersonation
289
290  if api_version == V1P4ALPHA1_API_VERSION:
291    return service.AnalyzeIamPolicy(
292        messages.CloudassetAnalyzeIamPolicyRequest(
293            accessSelector_permissions=permissions,
294            accessSelector_roles=roles,
295            identitySelector_identity=identity,
296            options_expandGroups=expand_groups,
297            options_expandResources=expand_resources,
298            options_expandRoles=expand_roles,
299            options_outputGroupEdges=output_group_edges,
300            options_outputPartialResultBeforeTimeout=output_partial_result_before_timeout,
301            options_outputResourceEdges=output_resource_edges,
302            parent=parent,
303            resourceSelector_fullResourceName=full_resource_name,
304        ))
305  elif api_version == V1P4BETA1_API_VERSION:
306    response = service.AnalyzeIamPolicy(
307        messages.CloudassetAnalyzeIamPolicyRequest(
308            analysisQuery_accessSelector_permissions=permissions,
309            analysisQuery_accessSelector_roles=roles,
310            analysisQuery_identitySelector_identity=identity,
311            analysisQuery_resourceSelector_fullResourceName=full_resource_name,
312            options_analyzeServiceAccountImpersonation=analyze_service_account_impersonation,
313            options_executionTimeout=execution_timeout,
314            options_expandGroups=expand_groups,
315            options_expandResources=expand_resources,
316            options_expandRoles=expand_roles,
317            options_outputGroupEdges=output_group_edges,
318            options_outputResourceEdges=output_resource_edges,
319            parent=parent,
320        ))
321  else:
322    response = service.AnalyzeIamPolicy(
323        messages.CloudassetAnalyzeIamPolicyRequest(
324            analysisQuery_accessSelector_permissions=permissions,
325            analysisQuery_accessSelector_roles=roles,
326            analysisQuery_identitySelector_identity=identity,
327            analysisQuery_options_analyzeServiceAccountImpersonation=analyze_service_account_impersonation,
328            analysisQuery_options_expandGroups=expand_groups,
329            analysisQuery_options_expandResources=expand_resources,
330            analysisQuery_options_expandRoles=expand_roles,
331            analysisQuery_options_outputGroupEdges=output_group_edges,
332            analysisQuery_options_outputResourceEdges=output_resource_edges,
333            analysisQuery_resourceSelector_fullResourceName=full_resource_name,
334            executionTimeout=execution_timeout,
335            scope=parent,
336        ))
337  if not args.show_response:
338    return _RenderResponseforAnalyzeIamPolicy(
339        response, analyze_service_account_impersonation)
340  return response
341
342
343class AnalyzeIamPolicyClient(object):
344  """Client for IAM policy analysis."""
345
346  def __init__(self, api_version=V1P4ALPHA1_API_VERSION):
347    self.api_version = api_version
348    self.client = GetClient(api_version)
349
350    if api_version == DEFAULT_API_VERSION:
351      self.service = self.client.v1
352    elif api_version == V1P4BETA1_API_VERSION:
353      self.service = self.client.v1p4beta1
354    else:
355      self.service = self.client.v1p4alpha1
356
357  def Analyze(self, args):
358    """Calls MakeAnalyzeIamPolicy method."""
359    messages = self.EncodeMessages(args)
360    return MakeAnalyzeIamPolicyHttpRequests(args, self.service, messages,
361                                            self.api_version)
362
363  def EncodeMessages(self, args):
364    """Adds custom encoding for MakeAnalyzeIamPolicy request."""
365    messages = GetMessages(self.api_version)
366
367    def AddCustomJsonFieldMapping(prefix, suffix):
368      field = _IAM_POLICY_ANALYZER_VERSION_DICT_JSON[
369          self.api_version][prefix] + suffix
370      encoding.AddCustomJsonFieldMapping(
371          messages.CloudassetAnalyzeIamPolicyRequest,
372          field,
373          field.replace('_', '.'),
374      )
375
376    AddCustomJsonFieldMapping('resource_selector', '_fullResourceName')
377    AddCustomJsonFieldMapping('identity_selector', '_identity')
378    AddCustomJsonFieldMapping('access_selector', '_roles')
379    AddCustomJsonFieldMapping('access_selector', '_permissions')
380    AddCustomJsonFieldMapping('options', '_expandGroups')
381    AddCustomJsonFieldMapping('options', '_expandResources')
382    AddCustomJsonFieldMapping('options', '_expandRoles')
383    AddCustomJsonFieldMapping('options', '_outputResourceEdges')
384    AddCustomJsonFieldMapping('options', '_outputGroupEdges')
385
386    if self.api_version == V1P4ALPHA1_API_VERSION and args.IsSpecified(
387        'output_partial_result_before_timeout'):
388      AddCustomJsonFieldMapping('options', '_outputPartialResultBeforeTimeout')
389
390    if self.api_version == V1P4BETA1_API_VERSION and args.IsSpecified(
391        'execution_timeout'):
392      AddCustomJsonFieldMapping('options', '_executionTimeout')
393
394    if self.api_version == V1P4BETA1_API_VERSION or self.api_version == DEFAULT_API_VERSION:
395      AddCustomJsonFieldMapping('options',
396                                '_analyzeServiceAccountImpersonation')
397
398    return messages
399
400
401class AssetExportClient(object):
402  """Client for export asset."""
403
404  def __init__(self, parent, api_version=DEFAULT_API_VERSION):
405    self.parent = parent
406    self.message_module = GetMessages(api_version)
407    if api_version == V1P7BETA1_API_VERSION:
408      self.service = GetClient(api_version).v1p7beta1
409    else:
410      self.service = GetClient(api_version).v1
411    self.api_version = api_version
412
413  def Export(self, args):
414    """Export assets with the asset export method."""
415    content_type = ContentTypeTranslation(args.content_type)
416    partition_key = PartitionKeyTranslation(args.partition_key)
417    partition_key = getattr(
418        self.message_module.PartitionSpec.PartitionKeyValueValuesEnum,
419        partition_key)
420    if args.output_path or args.output_path_prefix:
421      output_config = self.message_module.OutputConfig(
422          gcsDestination=self.message_module.GcsDestination(
423              uri=args.output_path, uriPrefix=args.output_path_prefix))
424    else:
425      source_ref = args.CONCEPTS.bigquery_table.Parse()
426      output_config = self.message_module.OutputConfig(
427          bigqueryDestination=self.message_module.BigQueryDestination(
428              dataset='projects/' + source_ref.projectId + '/datasets/' +
429              source_ref.datasetId,
430              table=source_ref.tableId,
431              force=args.force_,
432              partitionSpec=self.message_module.PartitionSpec(
433                  partitionKey=partition_key),
434              separateTablesPerAssetType=args.per_type_))
435    snapshot_time = None
436    if args.snapshot_time:
437      snapshot_time = times.FormatDateTime(args.snapshot_time)
438    if (self.api_version == V1P7BETA1_API_VERSION and
439        content_type == 'RELATIONSHIP'):
440      content_type = getattr(
441          self.message_module.ExportAssetsRequest.ContentTypeValueValuesEnum,
442          content_type)
443      export_assets_request = self.message_module.ExportAssetsRequest(
444          assetTypes=args.asset_types,
445          contentType=content_type,
446          outputConfig=output_config,
447          readTime=snapshot_time,
448          relationshipTypes=args.relationship_types)
449    elif content_type == 'RELATIONSHIP':
450      raise gcloud_exceptions.InvalidArgumentException('Export relationship'
451                                                       ' not supported')
452    else:
453      content_type = getattr(
454          self.message_module.ExportAssetsRequest.ContentTypeValueValuesEnum,
455          content_type)
456      export_assets_request = self.message_module.ExportAssetsRequest(
457          assetTypes=args.asset_types,
458          contentType=content_type,
459          outputConfig=output_config,
460          readTime=snapshot_time)
461    request_message = self.message_module.CloudassetExportAssetsRequest(
462        parent=self.parent, exportAssetsRequest=export_assets_request)
463    operation = self.service.ExportAssets(request_message)
464    return operation
465
466
467class AssetFeedClient(object):
468  """Client for asset feed."""
469
470  def __init__(self, parent, api_version=DEFAULT_API_VERSION):
471    self.parent = parent
472    self.message_module = GetMessages(api_version)
473    self.service = GetClient(api_version).feeds
474
475  def Create(self, args):
476    """Create a feed."""
477    content_type = ContentTypeTranslation(args.content_type)
478    content_type = getattr(self.message_module.Feed.ContentTypeValueValuesEnum,
479                           content_type)
480    feed_output_config = self.message_module.FeedOutputConfig(
481        pubsubDestination=self.message_module.PubsubDestination(
482            topic=args.pubsub_topic))
483    feed_condition = self.message_module.Expr(
484        expression=args.condition_expression,
485        title=args.condition_title,
486        description=args.condition_description)
487    feed = self.message_module.Feed(
488        assetNames=args.asset_names,
489        assetTypes=args.asset_types,
490        contentType=content_type,
491        feedOutputConfig=feed_output_config,
492        condition=feed_condition)
493    create_feed_request = self.message_module.CreateFeedRequest(
494        feed=feed, feedId=args.feed)
495    request_message = self.message_module.CloudassetFeedsCreateRequest(
496        parent=self.parent, createFeedRequest=create_feed_request)
497    return self.service.Create(request_message)
498
499  def Describe(self, args):
500    """Describe a feed."""
501    request_message = self.message_module.CloudassetFeedsGetRequest(
502        name='{}/feeds/{}'.format(self.parent, args.feed))
503    return self.service.Get(request_message)
504
505  def Delete(self, args):
506    """Delete a feed."""
507    request_message = self.message_module.CloudassetFeedsDeleteRequest(
508        name='{}/feeds/{}'.format(self.parent, args.feed))
509    self.service.Delete(request_message)
510
511  def List(self):
512    """List feeds under a parent."""
513    request_message = self.message_module.CloudassetFeedsListRequest(
514        parent=self.parent)
515    return self.service.List(request_message)
516
517  def Update(self, args):
518    """Update a feed."""
519    update_masks = []
520    content_type = ContentTypeTranslation(args.content_type)
521    content_type = getattr(self.message_module.Feed.ContentTypeValueValuesEnum,
522                           content_type)
523    feed_name = '{}/feeds/{}'.format(self.parent, args.feed)
524    if args.content_type or args.clear_content_type:
525      update_masks.append('content_type')
526    if args.pubsub_topic:
527      update_masks.append('feed_output_config.pubsub_destination.topic')
528    if args.condition_expression or args.clear_condition_expression:
529      update_masks.append('condition.expression')
530    if args.condition_title or args.clear_condition_title:
531      update_masks.append('condition.title')
532    if args.condition_description or args.clear_condition_description:
533      update_masks.append('condition.description')
534    asset_names, asset_types = self.UpdateAssetNamesAndTypes(
535        args, feed_name, update_masks)
536    update_mask = ','.join(update_masks)
537    feed_output_config = self.message_module.FeedOutputConfig(
538        pubsubDestination=self.message_module.PubsubDestination(
539            topic=args.pubsub_topic))
540    feed_condition = self.message_module.Expr(
541        expression=args.condition_expression,
542        title=args.condition_title,
543        description=args.condition_description)
544    feed = self.message_module.Feed(
545        assetNames=asset_names,
546        assetTypes=asset_types,
547        contentType=content_type,
548        feedOutputConfig=feed_output_config,
549        condition=feed_condition)
550    update_feed_request = self.message_module.UpdateFeedRequest(
551        feed=feed, updateMask=update_mask)
552    request_message = self.message_module.CloudassetFeedsPatchRequest(
553        name=feed_name, updateFeedRequest=update_feed_request)
554    return self.service.Patch(request_message)
555
556  def UpdateAssetNamesAndTypes(self, args, feed_name, update_masks):
557    """Get Updated assetNames and assetTypes."""
558    feed = self.service.Get(
559        self.message_module.CloudassetFeedsGetRequest(name=feed_name))
560    asset_names = repeated.ParsePrimitiveArgs(args, 'asset_names',
561                                              lambda: feed.assetNames)
562    if asset_names is not None:
563      update_masks.append('asset_names')
564    else:
565      asset_names = []
566    asset_types = repeated.ParsePrimitiveArgs(args, 'asset_types',
567                                              lambda: feed.assetTypes)
568    if asset_types is not None:
569      update_masks.append('asset_types')
570    else:
571      asset_types = []
572    return asset_names, asset_types
573
574
575class AssetSearchClient(object):
576  """Client for search assets."""
577
578  _DEFAULT_PAGE_SIZE = 20
579
580  def __init__(self, api_version):
581    self.message_module = GetMessages(api_version)
582    if api_version == V1P1BETA1_API_VERSION:
583      self.resource_service = GetClient(api_version).resources
584      self.search_all_resources_method = 'SearchAll'
585      self.search_all_resources_request = self.message_module.CloudassetResourcesSearchAllRequest
586      self.policy_service = GetClient(api_version).iamPolicies
587      self.search_all_iam_policies_method = 'SearchAll'
588      self.search_all_iam_policies_request = self.message_module.CloudassetIamPoliciesSearchAllRequest
589    else:
590      self.resource_service = GetClient(api_version).v1
591      self.search_all_resources_method = 'SearchAllResources'
592      self.search_all_resources_request = self.message_module.CloudassetSearchAllResourcesRequest
593      self.policy_service = GetClient(api_version).v1
594      self.search_all_iam_policies_method = 'SearchAllIamPolicies'
595      self.search_all_iam_policies_request = self.message_module.CloudassetSearchAllIamPoliciesRequest
596
597  def SearchAllResources(self, args):
598    """Calls SearchAllResources method."""
599    request = self.search_all_resources_request(
600        scope=asset_utils.GetDefaultScopeIfEmpty(args),
601        query=args.query,
602        assetTypes=args.asset_types,
603        orderBy=args.order_by)
604    return list_pager.YieldFromList(
605        self.resource_service,
606        request,
607        method=self.search_all_resources_method,
608        field='results',
609        batch_size=args.page_size or self._DEFAULT_PAGE_SIZE,
610        batch_size_attribute='pageSize',
611        current_token_attribute='pageToken',
612        next_token_attribute='nextPageToken')
613
614  def SearchAllIamPolicies(self, args):
615    """Calls SearchAllIamPolicies method."""
616    request = self.search_all_iam_policies_request(
617        scope=asset_utils.GetDefaultScopeIfEmpty(args), query=args.query)
618    return list_pager.YieldFromList(
619        self.policy_service,
620        request,
621        method=self.search_all_iam_policies_method,
622        field='results',
623        batch_size=args.page_size or self._DEFAULT_PAGE_SIZE,
624        batch_size_attribute='pageSize',
625        current_token_attribute='pageToken',
626        next_token_attribute='nextPageToken')
627
628
629class AssetListClient(object):
630  """Client for list assets."""
631
632  def __init__(self, parent, api_version=V1P5BETA1_API_VERSION):
633    self.parent = parent
634    self.message_module = GetMessages(api_version)
635    self.service = GetClient(api_version).assets
636
637  def List(self, args, do_filter=False):
638    """List assets with the asset list method."""
639    snapshot_time = None
640    if args.snapshot_time:
641      snapshot_time = times.FormatDateTime(args.snapshot_time)
642    content_type = ContentTypeTranslation(args.content_type)
643    list_assets_request = self.message_module.CloudassetAssetsListRequest(
644        parent=self.parent,
645        contentType=getattr(
646            self.message_module.CloudassetAssetsListRequest
647            .ContentTypeValueValuesEnum, content_type),
648        assetTypes=args.asset_types,
649        readTime=snapshot_time)
650    return list_pager.YieldFromList(
651        self.service,
652        list_assets_request,
653        field='assets',
654        limit=args.limit,
655        batch_size=args.page_size,
656        batch_size_attribute='pageSize',
657        current_token_attribute='pageToken',
658        next_token_attribute='nextPageToken',
659        predicate=args.filter_func if do_filter else None)
660
661
662class AssetOperationClient(object):
663  """Client for operations."""
664
665  def __init__(self, api_version=DEFAULT_API_VERSION):
666    self.service = GetClient(api_version).operations
667    self.message = GetMessages(api_version).CloudassetOperationsGetRequest
668
669  def Get(self, name):
670    request = self.message(name=name)
671    return self.service.Get(request)
672
673
674class GetHistoryClient(object):
675  """Client for get history assets."""
676
677  def __init__(self, api_version=DEFAULT_API_VERSION):
678    self.api_version = api_version
679    self.client = GetClient(api_version)
680    self.service = self.client.v1
681
682  def GetHistory(self, args):
683    return MakeGetAssetsHistoryHttpRequests(args, self.service,
684                                            self.api_version)
685
686
687class IamPolicyAnalysisLongrunningClient(object):
688  """Client for analyze IAM policy asynchronously."""
689
690  def __init__(self, api_version=DEFAULT_API_VERSION):
691    self.message_module = GetMessages(api_version)
692    if api_version == V1P4BETA1_API_VERSION:
693      self.service = GetClient(api_version).v1p4beta1
694    else:
695      self.service = GetClient(api_version).v1
696
697  def Analyze(self, scope, args, api_version=DEFAULT_API_VERSION):
698    """Analyze IAM Policy asynchronously."""
699    analysis_query = self.message_module.IamPolicyAnalysisQuery()
700    if api_version == V1P4BETA1_API_VERSION:
701      analysis_query.parent = scope
702    else:
703      analysis_query.scope = scope
704    if args.IsSpecified('full_resource_name'):
705      analysis_query.resourceSelector = self.message_module.ResourceSelector(
706          fullResourceName=args.full_resource_name)
707    if args.IsSpecified('identity'):
708      analysis_query.identitySelector = self.message_module.IdentitySelector(
709          identity=args.identity)
710    if args.IsSpecified('roles') or args.IsSpecified('permissions'):
711      analysis_query.accessSelector = self.message_module.AccessSelector()
712      if args.IsSpecified('roles'):
713        analysis_query.accessSelector.roles.extend(args.roles)
714      if args.IsSpecified('permissions'):
715        analysis_query.accessSelector.permissions.extend(args.permissions)
716
717    output_config = None
718    if api_version == V1P4BETA1_API_VERSION:
719      output_config = self.message_module.IamPolicyAnalysisOutputConfig(
720          gcsDestination=self.message_module.GcsDestination(
721              uri=args.output_path))
722    else:
723      if args.gcs_output_path:
724        output_config = self.message_module.IamPolicyAnalysisOutputConfig(
725            gcsDestination=self.message_module.GoogleCloudAssetV1GcsDestination(
726                uri=args.gcs_output_path))
727      else:
728        output_config = self.message_module.IamPolicyAnalysisOutputConfig(
729            bigqueryDestination=self.message_module
730            .GoogleCloudAssetV1BigQueryDestination(
731                dataset=args.bigquery_dataset,
732                tablePrefix=args.bigquery_table_prefix))
733        if args.IsSpecified('bigquery_partition_key'):
734          output_config.bigqueryDestination.partitionKey = getattr(
735              self.message_module.GoogleCloudAssetV1BigQueryDestination
736              .PartitionKeyValueValuesEnum, args.bigquery_partition_key)
737        if args.IsSpecified('bigquery_write_disposition'):
738          output_config.bigqueryDestination.writeDisposition = args.bigquery_write_disposition
739
740    options = self.message_module.Options()
741    if args.expand_groups:
742      options.expandGroups = args.expand_groups
743    if args.expand_resources:
744      options.expandResources = args.expand_resources
745    if args.expand_roles:
746      options.expandRoles = args.expand_roles
747    if args.output_resource_edges:
748      options.outputResourceEdges = args.output_resource_edges
749    if args.output_group_edges:
750      options.outputGroupEdges = args.output_group_edges
751    if args.analyze_service_account_impersonation:
752      options.analyzeServiceAccountImpersonation = args.analyze_service_account_impersonation
753
754    operation = None
755    if api_version == V1P4BETA1_API_VERSION:
756      request = self.message_module.ExportIamPolicyAnalysisRequest(
757          analysisQuery=analysis_query,
758          options=options,
759          outputConfig=output_config)
760      request_message = self.message_module.CloudassetExportIamPolicyAnalysisRequest(
761          parent=scope, exportIamPolicyAnalysisRequest=request)
762      operation = self.service.ExportIamPolicyAnalysis(request_message)
763    else:
764      analysis_query.options = options
765      request = self.message_module.AnalyzeIamPolicyLongrunningRequest(
766          analysisQuery=analysis_query, outputConfig=output_config)
767      request_message = self.message_module.CloudassetAnalyzeIamPolicyLongrunningRequest(
768          scope=scope, analyzeIamPolicyLongrunningRequest=request)
769      operation = self.service.AnalyzeIamPolicyLongrunning(request_message)
770
771    return operation
772
773
774class AnalyzeMoveClient(object):
775  """Client for analyzing resource move."""
776
777  def __init__(self, api_version=DEFAULT_API_VERSION):
778    self.api_version = api_version
779    self.message_module = GetMessages(api_version)
780    self.service = GetClient(api_version).v1
781
782  def AnalyzeMove(self, args):
783    """Analyze resource move."""
784    project = 'projects/' + args.project
785
786    if args.IsSpecified('destination_folder'):
787      destination = 'folders/' + args.destination_folder
788    else:
789      destination = 'organizations/' + args.destination_organization
790
791    scope = self.message_module.CloudassetAnalyzeMoveRequest.ViewValueValuesEnum.FULL
792    if args.blockers_only:
793      scope = self.message_module.CloudassetAnalyzeMoveRequest.ViewValueValuesEnum.BASIC
794
795    request_message = self.message_module.CloudassetAnalyzeMoveRequest(
796        destinationParent=destination, resource=project, view=scope)
797
798    return self.service.AnalyzeMove(request_message)
799