1# -*- coding: utf-8 -*- #
2# Copyright 2015 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"""Useful commands for interacting with the Cloud Resource Management API."""
16
17from __future__ import absolute_import
18from __future__ import division
19from __future__ import unicode_literals
20
21from apitools.base.py import list_pager
22
23from googlecloudsdk.api_lib.cloudresourcemanager import projects_util
24from googlecloudsdk.api_lib.resource_manager import folders
25from googlecloudsdk.command_lib.iam import iam_util
26
27DEFAULT_API_VERSION = projects_util.DEFAULT_API_VERSION
28
29
30def List(limit=None,
31         filter=None,  # pylint: disable=redefined-builtin
32         batch_size=500,
33         api_version=DEFAULT_API_VERSION):
34  """Make API calls to List active projects.
35
36  Args:
37    limit: The number of projects to limit the results to. This limit is passed
38      to the server and the server does the limiting.
39    filter: The client side filter expression.
40    batch_size: the number of projects to get with each request.
41    api_version: the version of the api
42
43  Returns:
44    Generator that yields projects
45  """
46  client = projects_util.GetClient(api_version)
47  messages = projects_util.GetMessages(api_version)
48  return list_pager.YieldFromList(
49      client.projects,
50      messages.CloudresourcemanagerProjectsListRequest(
51          filter=_AddActiveProjectFilterIfNotSpecified(filter)),
52      batch_size=batch_size,
53      limit=limit,
54      field='projects',
55      batch_size_attribute='pageSize')
56
57
58def _AddActiveProjectFilterIfNotSpecified(filter_expr):
59  if not filter_expr:
60    return 'lifecycleState:ACTIVE'
61  if 'lifecycleState' in filter_expr:
62    return filter_expr
63  return 'lifecycleState:ACTIVE AND ({})'.format(filter_expr)
64
65
66def Get(project_ref, api_version=DEFAULT_API_VERSION,
67        disable_api_enablement_check=False):
68  """Get project information."""
69  client = projects_util.GetClient(api_version)
70  # disable_api_enablement_check added to handle special case of
71  # setting config value core/project, see b/133841504/
72  if disable_api_enablement_check:
73    client.check_response_func = None
74  return client.projects.Get(
75      client.MESSAGES_MODULE.CloudresourcemanagerProjectsGetRequest(
76          projectId=project_ref.projectId))
77
78
79def Create(project_ref,
80           display_name=None,
81           parent=None,
82           labels=None,
83           api_version=DEFAULT_API_VERSION):
84  """Create a new project.
85
86  Args:
87    project_ref: The identifier for the project
88    display_name: Optional display name for the project
89    parent: Optional for the project (ex. folders/123 or organizations/5231)
90    labels: Optional labels to apply to the project
91    api_version: the version of the api
92
93  Returns:
94    An Operation object which can be used to check on the progress of the
95    project creation.
96  """
97  client = projects_util.GetClient(api_version)
98  messages = projects_util.GetMessages(api_version)
99  return client.projects.Create(
100      messages.Project(
101          projectId=project_ref.Name(),
102          name=display_name if display_name else project_ref.Name(),
103          parent=parent,
104          labels=labels))
105
106
107def Delete(project_ref, api_version=DEFAULT_API_VERSION):
108  """Delete an existing project."""
109  client = projects_util.GetClient(api_version)
110  messages = projects_util.GetMessages(api_version)
111
112  client.projects.Delete(
113      messages.CloudresourcemanagerProjectsDeleteRequest(
114          projectId=project_ref.Name()))
115  return projects_util.DeletedResource(project_ref.Name())
116
117
118def Undelete(project_ref, api_version=DEFAULT_API_VERSION):
119  """Undelete a project that has been deleted."""
120  client = projects_util.GetClient(api_version)
121  messages = projects_util.GetMessages(api_version)
122
123  client.projects.Undelete(
124      messages.CloudresourcemanagerProjectsUndeleteRequest(
125          projectId=project_ref.Name()))
126  return projects_util.DeletedResource(project_ref.Name())
127
128
129def Update(project_ref,
130           name=None,
131           parent=None,
132           labels_diff=None,
133           api_version=DEFAULT_API_VERSION):
134  """Update project information."""
135  client = projects_util.GetClient(api_version)
136  messages = projects_util.GetMessages(api_version)
137
138  project = client.projects.Get(
139      client.MESSAGES_MODULE.CloudresourcemanagerProjectsGetRequest(
140          projectId=project_ref.projectId))
141
142  if name:
143    project.name = name
144
145  if parent:
146    project.parent = parent
147
148  if labels_diff:
149    labels_update = labels_diff.Apply(messages.Project.LabelsValue,
150                                      project.labels)
151    if labels_update.needs_update:
152      project.labels = labels_update.labels
153
154  return client.projects.Update(project)
155
156
157def GetIamPolicy(project_ref, api_version=DEFAULT_API_VERSION):
158  """Get IAM policy for a given project."""
159  client = projects_util.GetClient(api_version)
160  messages = projects_util.GetMessages(api_version)
161
162  policy_request = messages.CloudresourcemanagerProjectsGetIamPolicyRequest(
163      getIamPolicyRequest=messages.GetIamPolicyRequest(
164          options=messages.GetPolicyOptions(
165              requestedPolicyVersion=
166              iam_util.MAX_LIBRARY_IAM_SUPPORTED_VERSION)),
167      resource=project_ref.Name(),
168  )
169  return client.projects.GetIamPolicy(policy_request)
170
171
172def GetAncestry(project_id, api_version=DEFAULT_API_VERSION):
173  """Get ancestry for a given project."""
174  client = projects_util.GetClient(api_version)
175  messages = projects_util.GetMessages(api_version)
176
177  ancestry_request = messages.CloudresourcemanagerProjectsGetAncestryRequest(
178      getAncestryRequest=messages.GetAncestryRequest(),
179      projectId=project_id,
180  )
181
182  return client.projects.GetAncestry(ancestry_request)
183
184
185def SetIamPolicy(project_ref,
186                 policy,
187                 update_mask=None,
188                 api_version=DEFAULT_API_VERSION):
189  """Set IAM policy, for a given project."""
190  client = projects_util.GetClient(api_version)
191  messages = projects_util.GetMessages(api_version)
192
193  policy.version = iam_util.MAX_LIBRARY_IAM_SUPPORTED_VERSION
194  set_iam_policy_request = messages.SetIamPolicyRequest(policy=policy)
195  # Only include update_mask if provided, otherwise, leave the field unset.
196  if update_mask is not None:
197    set_iam_policy_request.updateMask = update_mask
198
199  policy_request = messages.CloudresourcemanagerProjectsSetIamPolicyRequest(
200      resource=project_ref.Name(),
201      setIamPolicyRequest=set_iam_policy_request)
202  return client.projects.SetIamPolicy(policy_request)
203
204
205def SetIamPolicyFromFile(project_ref,
206                         policy_file,
207                         api_version=DEFAULT_API_VERSION):
208  """Read projects IAM policy from a file, and set it."""
209  messages = projects_util.GetMessages(api_version)
210  policy = iam_util.ParsePolicyFile(policy_file, messages.Policy)
211  update_mask = iam_util.ConstructUpdateMaskFromPolicy(policy_file)
212
213  # To preserve the existing set-iam-policy behavior of always overwriting
214  # bindings and etag, add bindings and etag to update_mask.
215  if 'bindings' not in update_mask:
216    update_mask += ',bindings'
217  if 'etag' not in update_mask:
218    update_mask += ',etag'
219
220  return SetIamPolicy(project_ref, policy, update_mask, api_version)
221
222
223def AddIamPolicyBinding(project_ref,
224                        member,
225                        role,
226                        api_version=DEFAULT_API_VERSION):
227  return AddIamPolicyBindings(project_ref, [(member, role)], api_version)
228
229
230def AddIamPolicyBindings(project_ref,
231                         member_roles,
232                         api_version=DEFAULT_API_VERSION):
233  """Adds iam bindings to project_ref's iam policy.
234
235  Args:
236    project_ref: The project for the binding
237    member_roles: List of 2-tuples of the form [(member, role), ...].
238    api_version: The version of the api
239
240  Returns:
241    The updated IAM Policy
242  """
243  messages = projects_util.GetMessages(api_version)
244
245  policy = GetIamPolicy(project_ref, api_version)
246  for member, role in member_roles:
247    iam_util.AddBindingToIamPolicy(messages.Binding, policy, member, role)
248  return SetIamPolicy(project_ref, policy, api_version=api_version)
249
250
251def AddIamPolicyBindingWithCondition(project_ref,
252                                     member,
253                                     role,
254                                     condition,
255                                     api_version=DEFAULT_API_VERSION):
256  """Add iam binding with condition to project_ref's iam policy."""
257  messages = projects_util.GetMessages(api_version)
258
259  policy = GetIamPolicy(project_ref, api_version=api_version)
260  iam_util.AddBindingToIamPolicyWithCondition(messages.Binding, messages.Expr,
261                                              policy, member, role, condition)
262  return SetIamPolicy(project_ref, policy, api_version=api_version)
263
264
265def RemoveIamPolicyBinding(project_ref,
266                           member,
267                           role,
268                           api_version=DEFAULT_API_VERSION):
269  policy = GetIamPolicy(project_ref, api_version=api_version)
270  iam_util.RemoveBindingFromIamPolicy(policy, member, role)
271  return SetIamPolicy(project_ref, policy, api_version=api_version)
272
273
274def RemoveIamPolicyBindingWithCondition(project_ref,
275                                        member,
276                                        role,
277                                        condition,
278                                        all_conditions,
279                                        api_version=DEFAULT_API_VERSION):
280  """Remove iam binding with condition from project_ref's iam policy."""
281  policy = GetIamPolicy(project_ref, api_version=api_version)
282  iam_util.RemoveBindingFromIamPolicyWithCondition(policy, member, role,
283                                                   condition, all_conditions)
284  return SetIamPolicy(project_ref, policy, api_version=api_version)
285
286
287def TestIamPermissions(project_ref,
288                       permissions,
289                       api_version=DEFAULT_API_VERSION):
290  """Return a subset of the given permissions that a caller has on project_ref."""
291  client = projects_util.GetClient(api_version)
292  messages = projects_util.GetMessages(api_version)
293
294  request = messages.CloudresourcemanagerProjectsTestIamPermissionsRequest(
295      resource=project_ref.Name(),
296      testIamPermissionsRequest=messages.TestIamPermissionsRequest(
297          permissions=permissions))
298  return client.projects.TestIamPermissions(request)
299
300
301def ParentNameToResourceId(parent_name, api_version=DEFAULT_API_VERSION):
302  messages = projects_util.GetMessages(api_version)
303  if not parent_name:
304    return None
305  elif parent_name.startswith('folders/'):
306    return messages.ResourceId(
307        id=folders.FolderNameToId(parent_name), type='folder')
308  elif parent_name.startswith('organizations/'):
309    return messages.ResourceId(
310        id=parent_name[len('organizations/'):], type='organization')
311