gcpdiag.queries.crm

Queries related to Resource Manager (projects, resources).
class Project(gcpdiag.models.Resource):
29class Project(models.Resource):
30  """Represents a Project resource.
31
32  See also the API documentation:
33  https://cloud.google.com/resource-manager/reference/rest/v3/projects/get
34  """
35  _id: str
36  _resource_data: dict
37  _number: int
38
39  def __init__(self, resource_data):
40    super().__init__(project_id=resource_data['projectId'])
41    self._id = resource_data['projectId']
42    self._resource_data = resource_data
43    match = re.match(r'projects/(\d+)$', resource_data['name'])
44    if not match:
45      raise ValueError(f'can\'t determine project id ({resource_data})')
46    self._number = int(match.group(1))
47
48  @property
49  def number(self) -> int:
50    return self._number
51
52  @property
53  def id(self) -> str:
54    """Project id (not project number)."""
55    return self._id
56
57  @property
58  def name(self) -> str:
59    return self._resource_data['displayName']
60
61  @property
62  def full_path(self) -> str:
63    return f'projects/{self._id}'
64
65  @property
66  def short_path(self) -> str:
67    return self._id
68
69  @property
70  def default_compute_service_account(self) -> str:
71    return f'{self.number}-compute@developer.gserviceaccount.com'
72
73  @property
74  def parent(self) -> str:
75    return self._resource_data['parent']

Represents a Project resource.

See also the API documentation: https://cloud.google.com/resource-manager/reference/rest/v3/projects/get

Project(resource_data)
39  def __init__(self, resource_data):
40    super().__init__(project_id=resource_data['projectId'])
41    self._id = resource_data['projectId']
42    self._resource_data = resource_data
43    match = re.match(r'projects/(\d+)$', resource_data['name'])
44    if not match:
45      raise ValueError(f'can\'t determine project id ({resource_data})')
46    self._number = int(match.group(1))
number: int
48  @property
49  def number(self) -> int:
50    return self._number
id: str
52  @property
53  def id(self) -> str:
54    """Project id (not project number)."""
55    return self._id

Project id (not project number).

name: str
57  @property
58  def name(self) -> str:
59    return self._resource_data['displayName']
full_path: str
61  @property
62  def full_path(self) -> str:
63    return f'projects/{self._id}'

Returns the full path of this resource.

Example: 'projects/gcpdiag-gke-1-9b90/zones/europe-west4-a/clusters/gke1'

short_path: str
65  @property
66  def short_path(self) -> str:
67    return self._id

Returns the short name for this resource.

Note that it isn't clear from this name what kind of resource it is.

Example: 'gke1'

default_compute_service_account: str
69  @property
70  def default_compute_service_account(self) -> str:
71    return f'{self.number}-compute@developer.gserviceaccount.com'
parent: str
73  @property
74  def parent(self) -> str:
75    return self._resource_data['parent']
@caching.cached_api_call
def get_project(project_id: str) -> Project:
 78@caching.cached_api_call
 79def get_project(project_id: str) -> Project:
 80  '''Attempts to retrieve project details for the supplied project id or number.
 81    If the project is found/accessible, it returns a Project object with the resource data.
 82    If the project cannot be retrieved, the application raises one of the exceptions below.
 83
 84    Args:
 85        project_id (str): The project id or number of
 86        the project (e.g., "123456789", "example-project").
 87
 88    Returns:
 89        Project: An object representing the project's full details.
 90
 91    Raises:
 92        utils.GcpApiError: If there is an issue calling the GCP/HTTP Error API.
 93
 94    Usage:
 95        When using project identifier from gcpdiag.models.Context
 96
 97        project = crm.get_project(context.project_id)
 98
 99        An unknown project identifier
100        try:
101          project = crm.get_project("123456789")
102        except:
103          # Handle exception
104        else:
105          # use project data
106  '''
107  try:
108    logging.debug('retrieving project %s ', project_id)
109    crm_api = apis.get_api('cloudresourcemanager', 'v3', project_id)
110    request = crm_api.projects().get(name=f'projects/{project_id}')
111    response = request.execute(num_retries=config.API_RETRIES)
112  except googleapiclient.errors.HttpError as e:
113    error = utils.GcpApiError(response=e)
114    if 'IAM_PERMISSION_DENIED' == error.reason:
115      print(
116          f'[ERROR]:Authenticated account doesn\'t have access to project details of {project_id}.'
117          f'\nExecute:\ngcloud projects add-iam-policy-binding {project_id} --role=roles/viewer'
118          '--member="user|group|serviceAccount:EMAIL_ACCOUNT"',
119          file=sys.stderr)
120    else:
121      print(f'[ERROR]:can\'t access project {project_id}: {error.message}.',
122            file=sys.stderr)
123    print(
124        f'[DEBUG]: An Http Error occurred whiles accessing projects.get \n\n{e}',
125        file=sys.stderr)
126    raise error from e
127  else:
128    return Project(resource_data=response)

Attempts to retrieve project details for the supplied project id or number. If the project is found/accessible, it returns a Project object with the resource data. If the project cannot be retrieved, the application raises one of the exceptions below.

Arguments:
  • project_id (str): The project id or number of
  • the project (e.g., "123456789", "example-project").
Returns:

Project: An object representing the project's full details.

Raises:
  • utils.GcpApiError: If there is an issue calling the GCP/HTTP Error API.
Usage:

When using project identifier from gcpdiag.models.Context

project = crm.get_project(context.project_id)

An unknown project identifier try: project = crm.get_project("123456789") except: # Handle exception else: # use project data

@caching.cached_api_call
def get_all_projects_in_parent(project_id: str) -> List[gcpdiag.queries.billing.ProjectBillingInfo]:
131@caching.cached_api_call
132def get_all_projects_in_parent(project_id: str) -> List[ProjectBillingInfo]:
133  """Get all projects in the Parent Folder that current user has
134  permission to view"""
135  projects: List[ProjectBillingInfo] = []
136  if (not project_id) or (not apis.is_enabled(project_id, 'cloudbilling')):
137    return projects
138  project = get_project(project_id)
139  p_filter = ('parent.type:' + project.parent.split('/')[0][:-1] +
140              ' parent.id:' +
141              project.parent.split('/')[1] if project.parent else '')
142
143  api = apis.get_api('cloudresourcemanager', 'v3')
144  for p in apis_utils.list_all(request=api.projects().search(query=p_filter),
145                               next_function=api.projects().search_next,
146                               response_keyword='projects'):
147    try:
148      crm_api = apis.get_api('cloudresourcemanager', 'v3', p['projectId'])
149      p_name = 'projects/' + p['projectId'] if 'projects/' not in p[
150          'projectId'] else p['projectId']
151      request = crm_api.projects().get(name=p_name)
152      response = request.execute(num_retries=config.API_RETRIES)
153      projects.append(get_billing_info(response['projectId']))
154    except (utils.GcpApiError, googleapiclient.errors.HttpError) as error:
155      if isinstance(error, googleapiclient.errors.HttpError):
156        error = utils.GcpApiError(error)
157      if error.reason in [
158          'IAM_PERMISSION_DENIED', 'USER_PROJECT_DENIED', 'SERVICE_DISABLED'
159      ]:
160        # skip projects that user does not have permissions on
161        continue
162      else:
163        print(
164            f'[ERROR]: An Http Error occurred whiles accessing projects.get \n\n{error}',
165            file=sys.stderr)
166      raise error from error
167  return projects

Get all projects in the Parent Folder that current user has permission to view

class Organization(gcpdiag.models.Resource):
170class Organization(models.Resource):
171  """Represents an Organization resource.
172
173  See also the API documentation:
174  https://cloud.google.com/resource-manager/reference/rest/v1/organizations/get
175  """
176  _resource_data: dict
177
178  def __init__(self, project_id, resource_data):
179    super().__init__(project_id=project_id)
180    self._resource_data = resource_data
181
182  @property
183  def id(self) -> str:
184    """The numeric organization ID."""
185    # Note: organization ID is returned in the format 'organizations/12345'
186    return self._resource_data['name'].split('/')[-1]
187
188  @property
189  def name(self) -> str:
190    """The organization's display name."""
191    return self._resource_data['displayName']
192
193  @property
194  def full_path(self) -> str:
195    return self._resource_data['name']
196
197  @property
198  def short_path(self) -> str:
199    return f"organizations/{self.id}"

Represents an Organization resource.

See also the API documentation: https://cloud.google.com/resource-manager/reference/rest/v1/organizations/get

Organization(project_id, resource_data)
178  def __init__(self, project_id, resource_data):
179    super().__init__(project_id=project_id)
180    self._resource_data = resource_data
id: str
182  @property
183  def id(self) -> str:
184    """The numeric organization ID."""
185    # Note: organization ID is returned in the format 'organizations/12345'
186    return self._resource_data['name'].split('/')[-1]

The numeric organization ID.

name: str
188  @property
189  def name(self) -> str:
190    """The organization's display name."""
191    return self._resource_data['displayName']

The organization's display name.

full_path: str
193  @property
194  def full_path(self) -> str:
195    return self._resource_data['name']

Returns the full path of this resource.

Example: 'projects/gcpdiag-gke-1-9b90/zones/europe-west4-a/clusters/gke1'

short_path: str
197  @property
198  def short_path(self) -> str:
199    return f"organizations/{self.id}"

Returns the short name for this resource.

Note that it isn't clear from this name what kind of resource it is.

Example: 'gke1'

@caching.cached_api_call
def get_organization( project_id: str, skip_error_print: bool = True) -> Organization | None:
202@caching.cached_api_call
203def get_organization(project_id: str,
204                     skip_error_print: bool = True) -> Organization | None:
205  """Retrieves the parent Organization for a given project.
206
207  This function first finds the project's ancestry to identify the
208  organization ID, then fetches the organization's details.
209
210  Args:
211      project_id (str): The ID of the project whose organization is to be fetched.
212
213  Returns:
214      An Organization object if the project belongs to an organization,
215      otherwise None.
216
217  Raises:
218        utils.GcpApiError: If there is an issue calling the GCP/HTTP Error API.
219  """
220  try:
221    logging.debug('retrieving ancestry for project %s', project_id)
222    crm_v1_api = apis.get_api('cloudresourcemanager', 'v1', project_id)
223    ancestry_request = crm_v1_api.projects().getAncestry(projectId=project_id)
224    ancestry_response = ancestry_request.execute(num_retries=config.API_RETRIES)
225
226    org_id = None
227    for ancestor in ancestry_response.get('ancestor', []):
228      if ancestor.get('resourceId', {}).get('type') == 'organization':
229        org_id = ancestor['resourceId']['id']
230        break
231
232    if not org_id:
233      logging.debug('project %s is not part of an organization', project_id)
234      return None
235
236    crm_v1_api = apis.get_api('cloudresourcemanager', 'v1', project_id)
237    org_request = crm_v1_api.organizations().get(name=f'organizations/{org_id}')
238    org_response = org_request.execute(num_retries=config.API_RETRIES)
239
240    return Organization(project_id=project_id, resource_data=org_response)
241
242  except googleapiclient.errors.HttpError as e:
243    error = utils.GcpApiError(response=e)
244    if not skip_error_print:
245      print(
246          f'[ERROR]: can\'t access organization for project {project_id}: {error.message}.',
247          file=sys.stderr)
248      print(
249          f'[DEBUG]: An Http Error occurred while accessing organization details \n\n{e}',
250          file=sys.stderr)
251    raise error from e

Retrieves the parent Organization for a given project.

This function first finds the project's ancestry to identify the organization ID, then fetches the organization's details.

Arguments:
  • project_id (str): The ID of the project whose organization is to be fetched.
Returns:

An Organization object if the project belongs to an organization, otherwise None.

Raises:
  • utils.GcpApiError: If there is an issue calling the GCP/HTTP Error API.