gcpdiag.queries.gce
40class InstanceTemplate(models.Resource): 41 """Represents a GCE Instance Template.""" 42 43 _resource_data: dict 44 45 def __init__(self, project_id, resource_data): 46 super().__init__(project_id=project_id) 47 self._resource_data = resource_data 48 49 @property 50 def self_link(self) -> str: 51 return self._resource_data['selfLink'] 52 53 @property 54 def full_path(self) -> str: 55 result = re.match(r'https://www.googleapis.com/compute/v1/(.*)', self.self_link) 56 if result: 57 return result.group(1) 58 else: 59 return f'>> {self.self_link}' 60 61 @property 62 def short_path(self) -> str: 63 path = self.project_id + '/' + self.name 64 return path 65 66 @property 67 def name(self) -> str: 68 return self._resource_data['name'] 69 70 @property 71 def tags(self) -> List[str]: 72 return self._resource_data['properties'].get('tags', {}).get('items', []) 73 74 @property 75 def service_account(self) -> Optional[str]: 76 sa_list = self._resource_data['properties'].get('serviceAccounts', []) 77 if not sa_list: 78 return None 79 email = sa_list[0]['email'] 80 if email == 'default': 81 project_nr = crm.get_project(self._project_id).number 82 return f'{project_nr}-compute@developer.gserviceaccount.com' 83 return email 84 85 @property 86 def network(self) -> network_q.Network: 87 return network_q.get_network_from_url( 88 self._resource_data['properties']['networkInterfaces'][0]['network'] 89 ) 90 91 @property 92 def subnetwork(self) -> network_q.Subnetwork: 93 subnet_url = self._resource_data['properties']['networkInterfaces'][0]['subnetwork'] 94 return self.network.subnetworks[subnet_url] 95 96 def get_metadata(self, key: str) -> str: 97 for item in self._resource_data['properties']['metadata']['items']: 98 if item['key'] == key: 99 return item['value'] 100 return ''
Represents a GCE Instance Template.
53 @property 54 def full_path(self) -> str: 55 result = re.match(r'https://www.googleapis.com/compute/v1/(.*)', self.self_link) 56 if result: 57 return result.group(1) 58 else: 59 return f'>> {self.self_link}'
Returns the full path of this resource.
Example: 'projects/gcpdiag-gke-1-9b90/zones/europe-west4-a/clusters/gke1'
61 @property 62 def short_path(self) -> str: 63 path = self.project_id + '/' + self.name 64 return path
Returns the short name for this resource.
Note that it isn't clear from this name what kind of resource it is.
Example: 'gke1'
74 @property 75 def service_account(self) -> Optional[str]: 76 sa_list = self._resource_data['properties'].get('serviceAccounts', []) 77 if not sa_list: 78 return None 79 email = sa_list[0]['email'] 80 if email == 'default': 81 project_nr = crm.get_project(self._project_id).number 82 return f'{project_nr}-compute@developer.gserviceaccount.com' 83 return email
103class InstanceGroup(models.Resource): 104 """Represents a GCE instance group.""" 105 106 _resource_data: dict 107 108 def __init__(self, project_id, resource_data): 109 super().__init__(project_id=project_id) 110 self._resource_data = resource_data 111 112 @property 113 def full_path(self) -> str: 114 result = re.match( 115 r'https://www.googleapis.com/compute/v1/(.*)', 116 self._resource_data['selfLink'], 117 ) 118 if result: 119 return result.group(1) 120 else: 121 return '>> ' + self._resource_data['selfLink'] 122 123 @property 124 def short_path(self) -> str: 125 path = self.project_id + '/' + self.name 126 return path 127 128 @property 129 def self_link(self) -> str: 130 return self._resource_data['selfLink'] 131 132 @property 133 def name(self) -> str: 134 return self._resource_data['name'] 135 136 @property 137 def named_ports(self) -> List[dict]: 138 if 'namedPorts' in self._resource_data: 139 return self._resource_data['namedPorts'] 140 return [] 141 142 def has_named_ports(self) -> bool: 143 if 'namedPorts' in self._resource_data: 144 return True 145 return False
Represents a GCE instance group.
112 @property 113 def full_path(self) -> str: 114 result = re.match( 115 r'https://www.googleapis.com/compute/v1/(.*)', 116 self._resource_data['selfLink'], 117 ) 118 if result: 119 return result.group(1) 120 else: 121 return '>> ' + self._resource_data['selfLink']
Returns the full path of this resource.
Example: 'projects/gcpdiag-gke-1-9b90/zones/europe-west4-a/clusters/gke1'
123 @property 124 def short_path(self) -> str: 125 path = self.project_id + '/' + self.name 126 return path
Returns the short name for this resource.
Note that it isn't clear from this name what kind of resource it is.
Example: 'gke1'
148class ManagedInstanceGroup(models.Resource): 149 """Represents a GCE managed instance group.""" 150 151 _resource_data: dict 152 _region: Optional[str] 153 154 def __init__(self, project_id, resource_data): 155 super().__init__(project_id=project_id) 156 self._resource_data = resource_data 157 self._region = None 158 159 @property 160 def full_path(self) -> str: 161 result = re.match( 162 r'https://www.googleapis.com/compute/v1/(.*)', 163 self._resource_data['selfLink'], 164 ) 165 if result: 166 return result.group(1) 167 else: 168 return '>> ' + self._resource_data['selfLink'] 169 170 @property 171 def short_path(self) -> str: 172 path = self.project_id + '/' + self.name 173 return path 174 175 def is_gke(self) -> bool: 176 """Is this managed instance group part of a GKE cluster? 177 178 Note that the results are based on heuristics (the mig name), 179 which is not ideal. 180 181 Returns: 182 bool: True if this managed instance group is part of a GKE cluster. 183 """ 184 185 # gke- is normal GKE, gk3- is GKE autopilot 186 return self.name.startswith('gke-') or self.name.startswith('gk3-') 187 188 @property 189 def self_link(self) -> str: 190 return self._resource_data['selfLink'] 191 192 @property 193 def name(self) -> str: 194 return self._resource_data['name'] 195 196 @property 197 def region(self) -> str: 198 if self._region is None: 199 if 'region' in self._resource_data: 200 m = re.search(r'/regions/([^/]+)$', self._resource_data['region']) 201 if not m: 202 raise RuntimeError( 203 "can't determine region of mig {} ({})".format(self.name, self._resource_data['region']) 204 ) 205 self._region = m.group(1) 206 elif 'zone' in self._resource_data: 207 m = re.search(r'/zones/([^/]+)$', self._resource_data['zone']) 208 if not m: 209 raise RuntimeError( 210 "can't determine region of mig {} ({})".format(self.name, self._resource_data['region']) 211 ) 212 zone = m.group(1) 213 self._region = utils.zone_region(zone) 214 else: 215 raise RuntimeError( 216 f"can't determine region of mig {self.name}, both region and zone aren't set!" 217 ) 218 return self._region 219 220 @property 221 def zone(self) -> Optional[str]: 222 if 'zone' in self._resource_data: 223 m = re.search(r'/zones/([^/]+)$', self._resource_data['zone']) 224 if not m: 225 raise RuntimeError( 226 "can't determine zone of mig {} ({})".format(self.name, self._resource_data['zone']) 227 ) 228 return m.group(1) 229 return None 230 231 def count_no_action_instances(self) -> int: 232 """number of instances in the mig that are running and have no scheduled actions.""" 233 return self._resource_data['currentActions']['none'] 234 235 def is_instance_member(self, project_id: str, region: str, instance_name: str): 236 """Given the project_id, region and instance name, is it a member of this MIG?""" 237 return ( 238 self.project_id == project_id 239 and self.region == region 240 and instance_name.startswith(self._resource_data['baseInstanceName']) 241 ) 242 243 @property 244 def template(self) -> InstanceTemplate: 245 if 'instanceTemplate' not in self._resource_data: 246 raise RuntimeError('instanceTemplate not set for MIG {self.name}') 247 248 m = re.match( 249 r'https://www.googleapis.com/compute/v1/(.*)', 250 self._resource_data['instanceTemplate'], 251 ) 252 253 if not m: 254 raise RuntimeError( 255 "can't parse instanceTemplate: %s" % self._resource_data['instanceTemplate'] 256 ) 257 template_self_link = m.group(1) 258 templates = get_instance_templates(self.project_id) 259 if template_self_link not in templates: 260 raise RuntimeError(f'instanceTemplate {template_self_link} for MIG {self.name} not found') 261 return templates[template_self_link] 262 263 @property 264 def version_target_reached(self) -> bool: 265 return get_path(self._resource_data, ('status', 'versionTarget', 'isReached')) 266 267 def get(self, path: str, default: Any = None) -> Any: 268 """Gets a value from resource_data using a dot-separated path.""" 269 return get_path(self._resource_data, tuple(path.split('.')), default=default)
Represents a GCE managed instance group.
159 @property 160 def full_path(self) -> str: 161 result = re.match( 162 r'https://www.googleapis.com/compute/v1/(.*)', 163 self._resource_data['selfLink'], 164 ) 165 if result: 166 return result.group(1) 167 else: 168 return '>> ' + self._resource_data['selfLink']
Returns the full path of this resource.
Example: 'projects/gcpdiag-gke-1-9b90/zones/europe-west4-a/clusters/gke1'
170 @property 171 def short_path(self) -> str: 172 path = self.project_id + '/' + self.name 173 return path
Returns the short name for this resource.
Note that it isn't clear from this name what kind of resource it is.
Example: 'gke1'
175 def is_gke(self) -> bool: 176 """Is this managed instance group part of a GKE cluster? 177 178 Note that the results are based on heuristics (the mig name), 179 which is not ideal. 180 181 Returns: 182 bool: True if this managed instance group is part of a GKE cluster. 183 """ 184 185 # gke- is normal GKE, gk3- is GKE autopilot 186 return self.name.startswith('gke-') or self.name.startswith('gk3-')
Is this managed instance group part of a GKE cluster?
Note that the results are based on heuristics (the mig name), which is not ideal.
Returns:
bool: True if this managed instance group is part of a GKE cluster.
196 @property 197 def region(self) -> str: 198 if self._region is None: 199 if 'region' in self._resource_data: 200 m = re.search(r'/regions/([^/]+)$', self._resource_data['region']) 201 if not m: 202 raise RuntimeError( 203 "can't determine region of mig {} ({})".format(self.name, self._resource_data['region']) 204 ) 205 self._region = m.group(1) 206 elif 'zone' in self._resource_data: 207 m = re.search(r'/zones/([^/]+)$', self._resource_data['zone']) 208 if not m: 209 raise RuntimeError( 210 "can't determine region of mig {} ({})".format(self.name, self._resource_data['region']) 211 ) 212 zone = m.group(1) 213 self._region = utils.zone_region(zone) 214 else: 215 raise RuntimeError( 216 f"can't determine region of mig {self.name}, both region and zone aren't set!" 217 ) 218 return self._region
220 @property 221 def zone(self) -> Optional[str]: 222 if 'zone' in self._resource_data: 223 m = re.search(r'/zones/([^/]+)$', self._resource_data['zone']) 224 if not m: 225 raise RuntimeError( 226 "can't determine zone of mig {} ({})".format(self.name, self._resource_data['zone']) 227 ) 228 return m.group(1) 229 return None
231 def count_no_action_instances(self) -> int: 232 """number of instances in the mig that are running and have no scheduled actions.""" 233 return self._resource_data['currentActions']['none']
number of instances in the mig that are running and have no scheduled actions.
235 def is_instance_member(self, project_id: str, region: str, instance_name: str): 236 """Given the project_id, region and instance name, is it a member of this MIG?""" 237 return ( 238 self.project_id == project_id 239 and self.region == region 240 and instance_name.startswith(self._resource_data['baseInstanceName']) 241 )
Given the project_id, region and instance name, is it a member of this MIG?
243 @property 244 def template(self) -> InstanceTemplate: 245 if 'instanceTemplate' not in self._resource_data: 246 raise RuntimeError('instanceTemplate not set for MIG {self.name}') 247 248 m = re.match( 249 r'https://www.googleapis.com/compute/v1/(.*)', 250 self._resource_data['instanceTemplate'], 251 ) 252 253 if not m: 254 raise RuntimeError( 255 "can't parse instanceTemplate: %s" % self._resource_data['instanceTemplate'] 256 ) 257 template_self_link = m.group(1) 258 templates = get_instance_templates(self.project_id) 259 if template_self_link not in templates: 260 raise RuntimeError(f'instanceTemplate {template_self_link} for MIG {self.name} not found') 261 return templates[template_self_link]
267 def get(self, path: str, default: Any = None) -> Any: 268 """Gets a value from resource_data using a dot-separated path.""" 269 return get_path(self._resource_data, tuple(path.split('.')), default=default)
Gets a value from resource_data using a dot-separated path.
272class Autoscaler(models.Resource): 273 """Represents a GCE Autoscaler.""" 274 275 _resource_data: dict 276 277 def __init__(self, project_id, resource_data): 278 super().__init__(project_id=project_id) 279 self._resource_data = resource_data 280 281 @property 282 def self_link(self) -> str: 283 return self._resource_data['selfLink'] 284 285 @property 286 def full_path(self) -> str: 287 result = re.match(r'https://www.googleapis.com/compute/v1/(.*)', self.self_link) 288 if result: 289 return result.group(1) 290 else: 291 return f'>> {self.self_link}' 292 293 @property 294 def name(self) -> str: 295 return self._resource_data['name'] 296 297 def get(self, path: str, default: Any = None) -> Any: 298 """Gets a value from resource_data using a dot-separated path.""" 299 return get_path(self._resource_data, tuple(path.split('.')), default=default)
Represents a GCE Autoscaler.
285 @property 286 def full_path(self) -> str: 287 result = re.match(r'https://www.googleapis.com/compute/v1/(.*)', self.self_link) 288 if result: 289 return result.group(1) 290 else: 291 return f'>> {self.self_link}'
Returns the full path of this resource.
Example: 'projects/gcpdiag-gke-1-9b90/zones/europe-west4-a/clusters/gke1'
297 def get(self, path: str, default: Any = None) -> Any: 298 """Gets a value from resource_data using a dot-separated path.""" 299 return get_path(self._resource_data, tuple(path.split('.')), default=default)
Gets a value from resource_data using a dot-separated path.
302class SerialPortOutput: 303 """Represents the full Serial Port Output (/dev/ttyS0 or COM1) of an instance. 304 305 contents is the full 1MB of the instance. 306 """ 307 308 _project_id: str 309 _instance_id: str 310 _contents: List[str] 311 312 def __init__(self, project_id, instance_id, contents): 313 self._project_id = project_id 314 self._instance_id = instance_id 315 self._contents = contents 316 317 @property 318 def contents(self) -> List[str]: 319 return self._contents 320 321 @property 322 def instance_id(self) -> str: 323 return self._instance_id
Represents the full Serial Port Output (/dev/ttyS0 or COM1) of an instance.
contents is the full 1MB of the instance.
326class Instance(models.Resource): 327 """Represents a GCE instance.""" 328 329 _resource_data: dict 330 _region: Optional[str] 331 332 def __init__(self, project_id, resource_data): 333 super().__init__(project_id=project_id) 334 self._resource_data = resource_data 335 self._metadata_dict = None 336 self._region = None 337 338 @property 339 def id(self) -> str: 340 return self._resource_data['id'] 341 342 @property 343 def name(self) -> str: 344 return self._resource_data['name'] 345 346 @property 347 def full_path(self) -> str: 348 result = re.match( 349 r'https://www.googleapis.com/compute/v1/(.*)', 350 self._resource_data['selfLink'], 351 ) 352 if result: 353 return result.group(1) 354 else: 355 return '>> ' + self._resource_data['selfLink'] 356 357 @property 358 def short_path(self) -> str: 359 # Note: instance names must be unique per project, 360 # so no need to add the zone. 361 path = self.project_id + '/' + self.name 362 return path 363 364 @property 365 def creation_timestamp(self) -> datetime: 366 """VM creation time, as a *naive* `datetime` object.""" 367 return ( 368 datetime.fromisoformat(self._resource_data['creationTimestamp']) 369 .astimezone(timezone.utc) 370 .replace(tzinfo=None) 371 ) 372 373 @property 374 def region(self) -> str: 375 if self._region is None: 376 if 'zone' in self._resource_data: 377 m = re.search(r'/zones/([^/]+)$', self._resource_data['zone']) 378 if not m: 379 raise RuntimeError( 380 "can't determine region of instance %s (%s)" 381 % (self.name, self._resource_data['region']) 382 ) 383 zone = m.group(1) 384 self._region = utils.zone_region(zone) 385 else: 386 raise RuntimeError(f"can't determine region of instance {self.name}, zone isn't set!") 387 return self._region 388 389 @property 390 def zone(self) -> str: 391 zone_uri = self._resource_data['zone'] 392 m = re.search(r'/zones/([^/]+)$', zone_uri) 393 if m: 394 return m.group(1) 395 else: 396 raise RuntimeError(f"can't determine zone of instance {self.name}") 397 398 @property 399 def disks(self) -> List[dict]: 400 if 'disks' in self._resource_data: 401 return self._resource_data['disks'] 402 return [] 403 404 @property 405 def boot_disk_licenses(self) -> List[str]: 406 """Returns license names associated with boot disk.""" 407 for disk in self.disks: 408 if disk.get('boot'): 409 return [ 410 license_str.partition('/global/licenses/')[2] for license_str in disk.get('licenses', []) 411 ] 412 return [] 413 414 @property 415 def guest_os_features(self) -> List[str]: 416 """Returns guestOsFeatures types associated with boot disk.""" 417 for disk in self.disks: 418 if disk.get('boot'): 419 return [f['type'] for f in disk.get('guestOsFeatures', [])] 420 return [] 421 422 @property 423 def startrestricted(self) -> bool: 424 return self._resource_data['startRestricted'] 425 426 def laststarttimestamp(self) -> str: 427 return self._resource_data['lastStartTimestamp'] 428 429 def laststoptimestamp(self) -> str: 430 if 'lastStopTimestamp' in self._resource_data: 431 return self._resource_data['lastStopTimestamp'] 432 return '' 433 434 def is_serial_port_logging_enabled(self) -> bool: 435 value = self.get_metadata('serial-port-logging-enable') 436 return bool(value and value.upper() in POSITIVE_BOOL_VALUES) 437 438 def is_oslogin_enabled(self) -> bool: 439 value = self.get_metadata('enable-oslogin') 440 return bool(value and value.upper() in POSITIVE_BOOL_VALUES) 441 442 def is_metadata_enabled(self, metadata_name) -> bool: 443 """Use to check for common boolean metadata value""" 444 value = self.get_metadata(metadata_name) 445 return bool(value and value.upper() in POSITIVE_BOOL_VALUES) 446 447 def has_label(self, label) -> bool: 448 return label in self.labels 449 450 def is_dataproc_instance(self) -> bool: 451 return self.has_label(DATAPROC_LABEL) 452 453 def is_gke_node(self) -> bool: 454 return self.has_label(GKE_LABEL) 455 456 @property 457 def is_preemptible_vm(self) -> bool: 458 return ( 459 'scheduling' in self._resource_data 460 and 'preemptible' in self._resource_data['scheduling'] 461 and self._resource_data['scheduling']['preemptible'] 462 ) 463 464 def min_cpu_platform(self) -> str: 465 if 'minCpuPlatform' in self._resource_data: 466 return self._resource_data['minCpuPlatform'] 467 return 'None' 468 469 @property 470 def created_by_mig(self) -> bool: 471 """Return bool indicating if the instance part of a mig. 472 473 MIG which were part of MIG however have been removed or terminated will 474 return True. 475 """ 476 created_by = self.get_metadata('created-by') 477 if created_by is None: 478 return False 479 480 created_by_match = re.match( 481 r'projects/([^/]+)/((?:regions|zones)/[^/]+/instanceGroupManagers/[^/]+)$', 482 created_by, 483 ) 484 if not created_by_match: 485 return False 486 return True 487 488 def is_windows_machine(self) -> bool: 489 if 'disks' in self._resource_data: 490 disks = next(iter(self._resource_data['disks'])) 491 if 'guestOsFeatures' in disks: 492 if 'WINDOWS' in [t['type'] for t in iter(disks['guestOsFeatures'])]: 493 return True 494 return False 495 496 def is_public_machine(self) -> bool: 497 if 'networkInterfaces' in self._resource_data: 498 return 'natIP' in str(self._resource_data['networkInterfaces']) 499 return False 500 501 def machine_type(self): 502 if 'machineType' in self._resource_data: 503 # return self._resource_data['machineType'] 504 machine_type_uri = self._resource_data['machineType'] 505 mt = re.search(r'/machineTypes/([^/]+)$', machine_type_uri) 506 if mt: 507 return mt.group(1) 508 else: 509 raise RuntimeError(f"can't determine machineType of instance {self.name}") 510 return None 511 512 def check_license(self, licenses: List[str]) -> bool: 513 """Checks that a license is contained in a given license list.""" 514 if 'disks' in self._resource_data: 515 for disk in self._resource_data['disks']: 516 if 'license' in str(disk): 517 for license_ in licenses: 518 for attached_license in disk['licenses']: 519 if license_ == attached_license.partition('/global/licenses/')[2]: 520 return True 521 return False 522 523 def get_boot_disk_image(self) -> str: 524 """Get VM's boot disk image.""" 525 boot_disk_image: str = '' 526 for disk in self.disks: 527 if disk.get('boot', False): 528 disk_source = disk.get('source', '') 529 m = re.search(r'/disks/([^/]+)$', disk_source) 530 if not m: 531 raise RuntimeError(f"can't determine name of boot disk {disk_source}") 532 disk_name = m.group(1) 533 gce_disk: Disk = get_disk(self.project_id, zone=self.zone, disk_name=disk_name) 534 return gce_disk.source_image 535 return boot_disk_image 536 537 @property 538 def is_sole_tenant_vm(self) -> bool: 539 return bool('nodeAffinities' in self._resource_data['scheduling']) 540 541 @property 542 def network(self) -> network_q.Network: 543 # 'https://www.googleapis.com/compute/v1/projects/gcpdiag-gce1-aaaa/global/networks/default' 544 network_string = self._resource_data['networkInterfaces'][0]['network'] 545 m = re.match(r'^.+/projects/([^/]+)/global/networks/([^/]+)$', network_string) 546 if not m: 547 raise RuntimeError("can't parse network string: %s" % network_string) 548 return network_q.get_network( 549 m.group(1), m.group(2), context=models.Context(project_id=m.group(1)) 550 ) 551 552 @property 553 def network_ips(self) -> List[network_q.IPv4AddrOrIPv6Addr]: 554 return [ 555 ipaddress.ip_address(nic['networkIP']) for nic in self._resource_data['networkInterfaces'] 556 ] 557 558 @property 559 def get_network_interfaces(self): 560 return self._resource_data['networkInterfaces'] 561 562 @property 563 def subnetworks(self) -> List[network_q.Subnetwork]: 564 subnetworks = [] 565 for nic in self._resource_data['networkInterfaces']: 566 subnetworks.append(network_q.get_subnetwork_from_url(nic['subnetwork'])) 567 return subnetworks 568 569 @property 570 def routes(self) -> List[network_q.Route]: 571 routes = [] 572 for nic in self._resource_data['networkInterfaces']: 573 for route in network_q.get_routes(self.project_id): 574 if nic['network'] == route.network: 575 if route.tags == []: 576 routes.append(route) 577 continue 578 else: 579 temp = [x for x in self.tags if x in route.tags] 580 if len(temp) > 0: 581 routes.append(route) 582 return routes 583 584 def get_network_ip_for_instance_interface( 585 self, network: str 586 ) -> Optional[network_q.IPv4NetOrIPv6Net]: 587 """Get the network ip for a nic given a network name.""" 588 for nic in self._resource_data['networkInterfaces']: 589 if nic.get('network') == network: 590 return ipaddress.ip_network(nic.get('networkIP')) 591 return None 592 593 def secure_boot_enabled(self) -> bool: 594 if 'shieldedInstanceConfig' in self._resource_data: 595 return self._resource_data['shieldedInstanceConfig']['enableSecureBoot'] 596 return False 597 598 @property 599 def access_scopes(self) -> List[str]: 600 if 'serviceAccounts' in self._resource_data: 601 saccts = self._resource_data['serviceAccounts'] 602 if isinstance(saccts, list) and len(saccts) >= 1: 603 return saccts[0].get('scopes', []) 604 return [] 605 606 @property 607 def service_account(self) -> Optional[str]: 608 if 'serviceAccounts' in self._resource_data: 609 saccts = self._resource_data['serviceAccounts'] 610 if isinstance(saccts, list) and len(saccts) >= 1: 611 return saccts[0]['email'] 612 return None 613 614 @property 615 def tags(self) -> List[str]: 616 if 'tags' in self._resource_data: 617 if 'items' in self._resource_data['tags']: 618 return self._resource_data['tags']['items'] 619 return [] 620 621 def get_metadata(self, key: str) -> str: 622 if not self._metadata_dict: 623 self._metadata_dict = {} 624 if 'metadata' in self._resource_data and 'items' in self._resource_data['metadata']: 625 for item in self._resource_data['metadata']['items']: 626 if 'key' in item and 'value' in item: 627 self._metadata_dict[item['key']] = item['value'] 628 project_metadata = get_project_metadata(self.project_id) 629 return self._metadata_dict.get(key, project_metadata.get(key)) 630 631 @property 632 def status(self) -> str: 633 """VM Status.""" 634 return self._resource_data.get('status', None) 635 636 @property 637 def is_running(self) -> bool: 638 """VM Status is indicated as running.""" 639 return self._resource_data.get('status', False) == 'RUNNING' 640 641 @property 642 def network_interface_count(self) -> int: 643 """Returns the number of network interfaces attached to the instance.""" 644 return len(self._resource_data.get('networkInterfaces', [])) 645 646 @property # type: ignore 647 @caching.cached_api_call(in_memory=True) 648 def mig(self) -> ManagedInstanceGroup: 649 """Return ManagedInstanceGroup that owns this instance. 650 651 Throws AttributeError in case it isn't MIG-managed. 652 """ 653 654 created_by = self.get_metadata('created-by') 655 if created_by is None: 656 raise AttributeError(f'instance {self.id} is not managed by a mig') 657 658 # Example created-by: 659 # "projects/12340002/zones/europe-west4-a/instanceGroupManagers/gke-gke1-default-pool-e5e20a34-grp" 660 # (note how it uses a project number and not a project id...) 661 created_by_match = re.match( 662 r'projects/([^/]+)/((?:regions|zones)/[^/]+/instanceGroupManagers/[^/]+)$', 663 created_by, 664 ) 665 if not created_by_match: 666 raise AttributeError(f'instance {self.id} is not managed by a mig (created-by={created_by})') 667 project = crm.get_project(created_by_match.group(1)) 668 669 mig_self_link = ( 670 f'https://www.googleapis.com/compute/v1/projects/{project.id}/{created_by_match.group(2)}' 671 ) 672 673 # Try to find a matching mig. 674 context = models.Context(project_id=self.project_id) 675 all_migs = list(get_managed_instance_groups(context).values()) + list( 676 get_region_managed_instance_groups(context).values() 677 ) 678 679 for mig in all_migs: 680 if mig.self_link == mig_self_link: 681 return mig 682 683 raise AttributeError(f'MIG not found for instance {self.id}. Created by: {created_by}') 684 685 @property 686 def labels(self) -> dict: 687 return self._resource_data.get('labels', {})
Represents a GCE instance.
346 @property 347 def full_path(self) -> str: 348 result = re.match( 349 r'https://www.googleapis.com/compute/v1/(.*)', 350 self._resource_data['selfLink'], 351 ) 352 if result: 353 return result.group(1) 354 else: 355 return '>> ' + self._resource_data['selfLink']
Returns the full path of this resource.
Example: 'projects/gcpdiag-gke-1-9b90/zones/europe-west4-a/clusters/gke1'
357 @property 358 def short_path(self) -> str: 359 # Note: instance names must be unique per project, 360 # so no need to add the zone. 361 path = self.project_id + '/' + self.name 362 return path
Returns the short name for this resource.
Note that it isn't clear from this name what kind of resource it is.
Example: 'gke1'
364 @property 365 def creation_timestamp(self) -> datetime: 366 """VM creation time, as a *naive* `datetime` object.""" 367 return ( 368 datetime.fromisoformat(self._resource_data['creationTimestamp']) 369 .astimezone(timezone.utc) 370 .replace(tzinfo=None) 371 )
VM creation time, as a naive datetime object.
373 @property 374 def region(self) -> str: 375 if self._region is None: 376 if 'zone' in self._resource_data: 377 m = re.search(r'/zones/([^/]+)$', self._resource_data['zone']) 378 if not m: 379 raise RuntimeError( 380 "can't determine region of instance %s (%s)" 381 % (self.name, self._resource_data['region']) 382 ) 383 zone = m.group(1) 384 self._region = utils.zone_region(zone) 385 else: 386 raise RuntimeError(f"can't determine region of instance {self.name}, zone isn't set!") 387 return self._region
404 @property 405 def boot_disk_licenses(self) -> List[str]: 406 """Returns license names associated with boot disk.""" 407 for disk in self.disks: 408 if disk.get('boot'): 409 return [ 410 license_str.partition('/global/licenses/')[2] for license_str in disk.get('licenses', []) 411 ] 412 return []
Returns license names associated with boot disk.
414 @property 415 def guest_os_features(self) -> List[str]: 416 """Returns guestOsFeatures types associated with boot disk.""" 417 for disk in self.disks: 418 if disk.get('boot'): 419 return [f['type'] for f in disk.get('guestOsFeatures', [])] 420 return []
Returns guestOsFeatures types associated with boot disk.
442 def is_metadata_enabled(self, metadata_name) -> bool: 443 """Use to check for common boolean metadata value""" 444 value = self.get_metadata(metadata_name) 445 return bool(value and value.upper() in POSITIVE_BOOL_VALUES)
Use to check for common boolean metadata value
469 @property 470 def created_by_mig(self) -> bool: 471 """Return bool indicating if the instance part of a mig. 472 473 MIG which were part of MIG however have been removed or terminated will 474 return True. 475 """ 476 created_by = self.get_metadata('created-by') 477 if created_by is None: 478 return False 479 480 created_by_match = re.match( 481 r'projects/([^/]+)/((?:regions|zones)/[^/]+/instanceGroupManagers/[^/]+)$', 482 created_by, 483 ) 484 if not created_by_match: 485 return False 486 return True
Return bool indicating if the instance part of a mig.
MIG which were part of MIG however have been removed or terminated will return True.
501 def machine_type(self): 502 if 'machineType' in self._resource_data: 503 # return self._resource_data['machineType'] 504 machine_type_uri = self._resource_data['machineType'] 505 mt = re.search(r'/machineTypes/([^/]+)$', machine_type_uri) 506 if mt: 507 return mt.group(1) 508 else: 509 raise RuntimeError(f"can't determine machineType of instance {self.name}") 510 return None
512 def check_license(self, licenses: List[str]) -> bool: 513 """Checks that a license is contained in a given license list.""" 514 if 'disks' in self._resource_data: 515 for disk in self._resource_data['disks']: 516 if 'license' in str(disk): 517 for license_ in licenses: 518 for attached_license in disk['licenses']: 519 if license_ == attached_license.partition('/global/licenses/')[2]: 520 return True 521 return False
Checks that a license is contained in a given license list.
523 def get_boot_disk_image(self) -> str: 524 """Get VM's boot disk image.""" 525 boot_disk_image: str = '' 526 for disk in self.disks: 527 if disk.get('boot', False): 528 disk_source = disk.get('source', '') 529 m = re.search(r'/disks/([^/]+)$', disk_source) 530 if not m: 531 raise RuntimeError(f"can't determine name of boot disk {disk_source}") 532 disk_name = m.group(1) 533 gce_disk: Disk = get_disk(self.project_id, zone=self.zone, disk_name=disk_name) 534 return gce_disk.source_image 535 return boot_disk_image
Get VM's boot disk image.
541 @property 542 def network(self) -> network_q.Network: 543 # 'https://www.googleapis.com/compute/v1/projects/gcpdiag-gce1-aaaa/global/networks/default' 544 network_string = self._resource_data['networkInterfaces'][0]['network'] 545 m = re.match(r'^.+/projects/([^/]+)/global/networks/([^/]+)$', network_string) 546 if not m: 547 raise RuntimeError("can't parse network string: %s" % network_string) 548 return network_q.get_network( 549 m.group(1), m.group(2), context=models.Context(project_id=m.group(1)) 550 )
569 @property 570 def routes(self) -> List[network_q.Route]: 571 routes = [] 572 for nic in self._resource_data['networkInterfaces']: 573 for route in network_q.get_routes(self.project_id): 574 if nic['network'] == route.network: 575 if route.tags == []: 576 routes.append(route) 577 continue 578 else: 579 temp = [x for x in self.tags if x in route.tags] 580 if len(temp) > 0: 581 routes.append(route) 582 return routes
584 def get_network_ip_for_instance_interface( 585 self, network: str 586 ) -> Optional[network_q.IPv4NetOrIPv6Net]: 587 """Get the network ip for a nic given a network name.""" 588 for nic in self._resource_data['networkInterfaces']: 589 if nic.get('network') == network: 590 return ipaddress.ip_network(nic.get('networkIP')) 591 return None
Get the network ip for a nic given a network name.
621 def get_metadata(self, key: str) -> str: 622 if not self._metadata_dict: 623 self._metadata_dict = {} 624 if 'metadata' in self._resource_data and 'items' in self._resource_data['metadata']: 625 for item in self._resource_data['metadata']['items']: 626 if 'key' in item and 'value' in item: 627 self._metadata_dict[item['key']] = item['value'] 628 project_metadata = get_project_metadata(self.project_id) 629 return self._metadata_dict.get(key, project_metadata.get(key))
631 @property 632 def status(self) -> str: 633 """VM Status.""" 634 return self._resource_data.get('status', None)
VM Status.
636 @property 637 def is_running(self) -> bool: 638 """VM Status is indicated as running.""" 639 return self._resource_data.get('status', False) == 'RUNNING'
VM Status is indicated as running.
641 @property 642 def network_interface_count(self) -> int: 643 """Returns the number of network interfaces attached to the instance.""" 644 return len(self._resource_data.get('networkInterfaces', []))
Returns the number of network interfaces attached to the instance.
646 @property # type: ignore 647 @caching.cached_api_call(in_memory=True) 648 def mig(self) -> ManagedInstanceGroup: 649 """Return ManagedInstanceGroup that owns this instance. 650 651 Throws AttributeError in case it isn't MIG-managed. 652 """ 653 654 created_by = self.get_metadata('created-by') 655 if created_by is None: 656 raise AttributeError(f'instance {self.id} is not managed by a mig') 657 658 # Example created-by: 659 # "projects/12340002/zones/europe-west4-a/instanceGroupManagers/gke-gke1-default-pool-e5e20a34-grp" 660 # (note how it uses a project number and not a project id...) 661 created_by_match = re.match( 662 r'projects/([^/]+)/((?:regions|zones)/[^/]+/instanceGroupManagers/[^/]+)$', 663 created_by, 664 ) 665 if not created_by_match: 666 raise AttributeError(f'instance {self.id} is not managed by a mig (created-by={created_by})') 667 project = crm.get_project(created_by_match.group(1)) 668 669 mig_self_link = ( 670 f'https://www.googleapis.com/compute/v1/projects/{project.id}/{created_by_match.group(2)}' 671 ) 672 673 # Try to find a matching mig. 674 context = models.Context(project_id=self.project_id) 675 all_migs = list(get_managed_instance_groups(context).values()) + list( 676 get_region_managed_instance_groups(context).values() 677 ) 678 679 for mig in all_migs: 680 if mig.self_link == mig_self_link: 681 return mig 682 683 raise AttributeError(f'MIG not found for instance {self.id}. Created by: {created_by}')
Return ManagedInstanceGroup that owns this instance.
Throws AttributeError in case it isn't MIG-managed.
690class Disk(models.Resource): 691 """Represents a GCE disk.""" 692 693 _resource_data: dict 694 695 def __init__(self, project_id, resource_data): 696 super().__init__(project_id=project_id) 697 self._resource_data = resource_data 698 699 @property 700 def id(self) -> str: 701 return self._resource_data['id'] 702 703 @property 704 def name(self) -> str: 705 return self._resource_data['name'] 706 707 @property 708 def type(self) -> str: 709 disk_type = re.search(r'/diskTypes/([^/]+)$', self._resource_data['type']) 710 if not disk_type: 711 raise RuntimeError( 712 "can't determine type of the disk {} ({})".format(self.name, self._resource_data['type']) 713 ) 714 return disk_type.group(1) 715 716 @property 717 def users(self) -> list: 718 pattern = r'/instances/(.+)$' 719 # Extracting the instances 720 instances = [] 721 for i in self._resource_data.get('users', []): 722 m = re.search(pattern, i) 723 if m: 724 instances.append(m.group(1)) 725 return instances 726 727 @property 728 def zone(self) -> str: 729 m = re.search(r'/zones/([^/]+)$', self._resource_data['zone']) 730 if not m: 731 raise RuntimeError( 732 "can't determine zone of disk {} ({})".format(self.name, self._resource_data['zone']) 733 ) 734 return m.group(1) 735 736 @property 737 def source_image(self) -> str: 738 return self._resource_data.get('sourceImage', '') 739 740 @property 741 def full_path(self) -> str: 742 result = re.match( 743 r'https://www.googleapis.com/compute/v1/(.*)', 744 self._resource_data['selfLink'], 745 ) 746 if result: 747 return result.group(1) 748 else: 749 return '>> ' + self._resource_data['selfLink'] 750 751 @property 752 def short_path(self) -> str: 753 return f'{self.project_id}/{self.name}' 754 755 @property 756 def bootable(self) -> bool: 757 return 'guestOsFeatures' in self._resource_data 758 759 @property 760 def in_use(self) -> bool: 761 return 'users' in self._resource_data 762 763 @property 764 def size(self) -> int: 765 return self._resource_data['sizeGb'] 766 767 @property 768 def provisionediops(self) -> Optional[int]: 769 return self._resource_data.get('provisionedIops') 770 771 @property 772 def has_snapshot_schedule(self) -> bool: 773 return 'resourcePolicies' in self._resource_data
Represents a GCE disk.
740 @property 741 def full_path(self) -> str: 742 result = re.match( 743 r'https://www.googleapis.com/compute/v1/(.*)', 744 self._resource_data['selfLink'], 745 ) 746 if result: 747 return result.group(1) 748 else: 749 return '>> ' + self._resource_data['selfLink']
Returns the full path of this resource.
Example: 'projects/gcpdiag-gke-1-9b90/zones/europe-west4-a/clusters/gke1'
Returns the short name for this resource.
Note that it isn't clear from this name what kind of resource it is.
Example: 'gke1'
776@caching.cached_api_call(in_memory=True) 777def get_gce_zones(project_id: str) -> Set[str]: 778 try: 779 gce_api = apis.get_api('compute', 'v1', project_id) 780 logging.debug('listing gce zones of project %s', project_id) 781 request = gce_api.zones().list(project=project_id) 782 response = request.execute(num_retries=config.API_RETRIES) 783 if not response or 'items' not in response: 784 return set() 785 return {item['name'] for item in response['items'] if 'name' in item} 786 except googleapiclient.errors.HttpError as err: 787 raise utils.GcpApiError(err) from err
790def get_gce_public_licences(project_id: str) -> List[str]: 791 """Returns a list of licenses based on publicly available image project""" 792 licenses = [] 793 gce_api = apis.get_api('compute', 'v1', project_id) 794 logging.debug('listing licenses of project %s', project_id) 795 request = gce_api.licenses().list(project=project_id) 796 while request is not None: 797 response = request.execute() 798 for license_ in response['items']: 799 formatted_license = license_['selfLink'].partition('/global/licenses/')[2] 800 licenses.append(formatted_license) 801 request = gce_api.licenses().list_next(previous_request=request, previous_response=response) 802 return licenses
Returns a list of licenses based on publicly available image project
805def get_instance(project_id: str, zone: str, instance_name: str) -> Instance: 806 """Returns instance object matching instance name and zone""" 807 compute = apis.get_api('compute', 'v1', project_id) 808 request = compute.instances().get(project=project_id, zone=zone, instance=instance_name) 809 810 response = request.execute(num_retries=config.API_RETRIES) 811 return Instance(project_id, resource_data=response)
Returns instance object matching instance name and zone
814@caching.cached_api_call(in_memory=True) 815def get_instance_by_id(project_id: str, instance_id: str) -> Optional[Instance]: 816 """Returns instance object matching instance id in the project. 817 818 Searches all zones. 819 820 Args: 821 project_id: The ID of the GCP project. 822 instance_id: The unique ID of the GCE instance. 823 """ 824 if not apis.is_enabled(project_id, 'compute'): 825 return None 826 gce_api = apis.get_api('compute', 'v1', project_id) 827 # Use aggregatedList with filter to efficiently find the instance by ID. 828 request = gce_api.instances().aggregatedList( 829 project=project_id, 830 filter=f'id eq {instance_id}', 831 returnPartialSuccess=True, 832 ) 833 834 while request: 835 response = request.execute(num_retries=config.API_RETRIES) 836 items = response.get('items', {}) 837 for _, data in items.items(): 838 if 'instances' in data: 839 for instance_data in data['instances']: 840 if str(instance_data.get('id')) == str(instance_id): 841 return Instance(project_id, instance_data) 842 843 request = gce_api.instances().aggregatedList_next( 844 previous_request=request, previous_response=response 845 ) 846 847 return None
Returns instance object matching instance id in the project.
Searches all zones.
Arguments:
- project_id: The ID of the GCP project.
- instance_id: The unique ID of the GCE instance.
850@caching.cached_api_call(in_memory=True) 851def get_global_operations( 852 project: str, 853 filter_str: Optional[str] = None, 854 order_by: Optional[str] = None, 855 max_results: Optional[int] = None, 856 service_project_number: Optional[int] = None, 857) -> List[Dict[str, Any]]: 858 """Returns global operations object matching project id.""" 859 compute = apis.get_api('compute', 'v1', project) 860 logging.debug( 861 'searching compute global operationslogs in project %s with filter %s', 862 project, 863 filter_str, 864 ) 865 operations: List[Dict[str, Any]] = [] 866 request = compute.globalOperations().aggregatedList( 867 project=project, 868 filter=filter_str, 869 orderBy=order_by, 870 maxResults=max_results, 871 serviceProjectNumber=service_project_number, 872 returnPartialSuccess=True, 873 ) 874 while request: 875 response = request.execute(num_retries=config.API_RETRIES) 876 operations_by_regions = response.get('items', {}) 877 for _, data in operations_by_regions.items(): 878 if 'operations' not in data: 879 continue 880 operations.extend(data['operations']) 881 request = compute.globalOperations().aggregatedList_next( 882 previous_request=request, previous_response=response 883 ) 884 return operations
Returns global operations object matching project id.
887@caching.cached_api_call(in_memory=True) 888def get_disk(project_id: str, zone: str, disk_name: str) -> Disk: 889 """Returns disk object matching disk name and zone.""" 890 compute = apis.get_api('compute', 'v1', project_id) 891 request = compute.disks().get(project=project_id, zone=zone, disk=disk_name) 892 response = request.execute(num_retries=config.API_RETRIES) 893 return Disk(project_id, resource_data=response)
Returns disk object matching disk name and zone.
896def get_instance_group_manager( 897 project_id: str, zone: str, instance_group_manager_name: str 898) -> ManagedInstanceGroup: 899 """Get a zonal ManagedInstanceGroup object by name and zone. 900 901 Args: 902 project_id: The project ID of the instance group manager. 903 zone: The zone of the instance group manager. 904 instance_group_manager_name: The name of the instance group manager. 905 906 Returns: 907 A ManagedInstanceGroup object. 908 909 Raises: 910 utils.GcpApiError: If the API call fails. 911 """ 912 compute = apis.get_api('compute', 'v1', project_id) 913 request = compute.instanceGroupManagers().get( 914 project=project_id, 915 zone=zone, 916 instanceGroupManager=instance_group_manager_name, 917 ) 918 try: 919 response = request.execute(num_retries=config.API_RETRIES) 920 return ManagedInstanceGroup(project_id, resource_data=response) 921 except googleapiclient.errors.HttpError as err: 922 raise utils.GcpApiError(err) from err
Get a zonal ManagedInstanceGroup object by name and zone.
Arguments:
- project_id: The project ID of the instance group manager.
- zone: The zone of the instance group manager.
- instance_group_manager_name: The name of the instance group manager.
Returns:
A ManagedInstanceGroup object.
Raises:
- utils.GcpApiError: If the API call fails.
925def get_region_instance_group_manager( 926 project_id: str, region: str, instance_group_manager_name: str 927) -> ManagedInstanceGroup: 928 """Get a regional ManagedInstanceGroup object by name and region. 929 930 Args: 931 project_id: The project ID of the instance group manager. 932 region: The region of the instance group manager. 933 instance_group_manager_name: The name of the instance group manager. 934 935 Returns: 936 A ManagedInstanceGroup object. 937 938 Raises: 939 utils.GcpApiError: If the API call fails. 940 """ 941 compute = apis.get_api('compute', 'v1', project_id) 942 request = compute.regionInstanceGroupManagers().get( 943 project=project_id, 944 region=region, 945 instanceGroupManager=instance_group_manager_name, 946 ) 947 try: 948 response = request.execute(num_retries=config.API_RETRIES) 949 return ManagedInstanceGroup(project_id, resource_data=response) 950 except googleapiclient.errors.HttpError as err: 951 raise utils.GcpApiError(err) from err
Get a regional ManagedInstanceGroup object by name and region.
Arguments:
- project_id: The project ID of the instance group manager.
- region: The region of the instance group manager.
- instance_group_manager_name: The name of the instance group manager.
Returns:
A ManagedInstanceGroup object.
Raises:
- utils.GcpApiError: If the API call fails.
954def get_autoscaler(project_id: str, zone: str, autoscaler_name: str) -> Autoscaler: 955 """Get a zonal Autoscaler object by name and zone.""" 956 compute = apis.get_api('compute', 'v1', project_id) 957 request = compute.autoscalers().get(project=project_id, zone=zone, autoscaler=autoscaler_name) 958 try: 959 response = request.execute(num_retries=config.API_RETRIES) 960 return Autoscaler(project_id, resource_data=response) 961 except googleapiclient.errors.HttpError as err: 962 raise utils.GcpApiError(err) from err
Get a zonal Autoscaler object by name and zone.
965def get_region_autoscaler(project_id: str, region: str, autoscaler_name: str) -> Autoscaler: 966 """Get a regional Autoscaler object by name and region.""" 967 compute = apis.get_api('compute', 'v1', project_id) 968 request = compute.regionAutoscalers().get( 969 project=project_id, region=region, autoscaler=autoscaler_name 970 ) 971 try: 972 response = request.execute(num_retries=config.API_RETRIES) 973 return Autoscaler(project_id, resource_data=response) 974 except googleapiclient.errors.HttpError as err: 975 raise utils.GcpApiError(err) from err
Get a regional Autoscaler object by name and region.
978@caching.cached_api_call(in_memory=True) 979def get_instances(context: models.Context) -> Mapping[str, Instance]: 980 """Get a list of Instance matching the given context, indexed by instance id.""" 981 982 instances: Dict[str, Instance] = {} 983 if not apis.is_enabled(context.project_id, 'compute'): 984 return instances 985 gce_api = apis.get_api('compute', 'v1', context.project_id) 986 request = gce_api.instances().aggregatedList( 987 project=context.project_id, returnPartialSuccess=True 988 ) 989 logging.debug('listing gce instances of project %s', context.project_id) 990 while request: # Continue as long as there are pages 991 response = request.execute(num_retries=config.API_RETRIES) 992 instances_by_zones = response.get('items', {}) 993 for _, data_ in instances_by_zones.items(): 994 if 'instances' not in data_: 995 continue 996 for instance in data_['instances']: 997 result = re.match( 998 r'https://www.googleapis.com/compute/v1/projects/[^/]+/zones/([^/]+)/', 999 instance['selfLink'], 1000 ) 1001 if not result: 1002 logging.error( 1003 "instance %s selfLink didn't match regexp: %s", 1004 instance['id'], 1005 instance['selfLink'], 1006 ) 1007 continue 1008 zone = result.group(1) 1009 labels = instance.get('labels', {}) 1010 if not context.match_project_resource( 1011 resource=instance.get('name'), location=zone, labels=labels 1012 ) and not context.match_project_resource( 1013 resource=instance.get('id'), location=zone, labels=labels 1014 ): 1015 continue 1016 instances.update( 1017 {instance['id']: Instance(project_id=context.project_id, resource_data=instance)} 1018 ) 1019 request = gce_api.instances().aggregatedList_next( 1020 previous_request=request, previous_response=response 1021 ) 1022 return instances
Get a list of Instance matching the given context, indexed by instance id.
1025@caching.cached_api_call(in_memory=True) 1026def get_instance_groups(context: models.Context) -> Mapping[str, InstanceGroup]: 1027 """Get a list of InstanceGroups matching the given context, indexed by name.""" 1028 groups: Dict[str, InstanceGroup] = {} 1029 if not apis.is_enabled(context.project_id, 'compute'): 1030 return groups 1031 gce_api = apis.get_api('compute', 'v1', context.project_id) 1032 request = gce_api.instanceGroups().aggregatedList( 1033 project=context.project_id, returnPartialSuccess=True 1034 ) 1035 logging.debug('listing gce instance groups of project %s', context.project_id) 1036 while request: # Continue as long as there are pages 1037 response = request.execute(num_retries=config.API_RETRIES) 1038 groups_by_zones = response.get('items', {}) 1039 for _, data_ in groups_by_zones.items(): 1040 if 'instanceGroups' not in data_: 1041 continue 1042 for group in data_['instanceGroups']: 1043 result = re.match( 1044 r'https://www.googleapis.com/compute/v1/projects/[^/]+/(zones|regions)/([^/]+)', 1045 group['selfLink'], 1046 ) 1047 if not result: 1048 logging.error( 1049 "instance %s selfLink didn't match regexp: %s", 1050 group['id'], 1051 group['selfLink'], 1052 ) 1053 continue 1054 location = result.group(2) 1055 labels = group.get('labels', {}) 1056 resource = group.get('name', '') 1057 if not context.match_project_resource(location=location, labels=labels, resource=resource): 1058 continue 1059 instance_group = InstanceGroup(context.project_id, resource_data=group) 1060 groups[instance_group.full_path] = instance_group 1061 request = gce_api.instanceGroups().aggregatedList_next( 1062 previous_request=request, previous_response=response 1063 ) 1064 return groups
Get a list of InstanceGroups matching the given context, indexed by name.
1067@caching.cached_api_call(in_memory=True) 1068def get_managed_instance_groups( 1069 context: models.Context, 1070) -> Mapping[int, ManagedInstanceGroup]: 1071 """Get a list of zonal ManagedInstanceGroups matching the given context, indexed by mig id.""" 1072 1073 migs: Dict[int, ManagedInstanceGroup] = {} 1074 if not apis.is_enabled(context.project_id, 'compute'): 1075 return migs 1076 gce_api = apis.get_api('compute', 'v1', context.project_id) 1077 request = gce_api.instanceGroupManagers().aggregatedList( 1078 project=context.project_id, returnPartialSuccess=True 1079 ) 1080 logging.debug('listing zonal managed instance groups of project %s', context.project_id) 1081 while request: # Continue as long as there are pages 1082 response = request.execute(num_retries=config.API_RETRIES) 1083 migs_by_zones = response.get('items', {}) 1084 for _, data_ in migs_by_zones.items(): 1085 if 'instanceGroupManagers' not in data_: 1086 continue 1087 for mig in data_['instanceGroupManagers']: 1088 result = re.match( 1089 r'https://www.googleapis.com/compute/v1/projects/[^/]+/(?:regions|zones)/([^/]+)/', 1090 mig['selfLink'], 1091 ) 1092 if not result: 1093 logging.error( 1094 "mig %s selfLink didn't match regexp: %s", 1095 mig['name'], 1096 mig['selfLink'], 1097 ) 1098 continue 1099 location = result.group(1) 1100 labels = mig.get('labels', {}) 1101 resource = mig.get('name', '') 1102 if not context.match_project_resource(location=location, labels=labels, resource=resource): 1103 continue 1104 migs[mig['id']] = ManagedInstanceGroup(project_id=context.project_id, resource_data=mig) 1105 request = gce_api.instanceGroupManagers().aggregatedList_next( 1106 previous_request=request, previous_response=response 1107 ) 1108 return migs
Get a list of zonal ManagedInstanceGroups matching the given context, indexed by mig id.
1111@caching.cached_api_call(in_memory=True) 1112def get_region_managed_instance_groups( 1113 context: models.Context, 1114) -> Mapping[int, ManagedInstanceGroup]: 1115 """Get a list of regional ManagedInstanceGroups matching the given context, indexed by mig id.""" 1116 1117 migs: Dict[int, ManagedInstanceGroup] = {} 1118 if not apis.is_enabled(context.project_id, 'compute'): 1119 return migs 1120 gce_api = apis.get_api('compute', 'v1', context.project_id) 1121 requests = [ 1122 gce_api.regionInstanceGroupManagers().list(project=context.project_id, region=r.name) 1123 for r in get_all_regions(context.project_id) 1124 ] 1125 logging.debug( 1126 'listing regional managed instance groups of project %s', 1127 context.project_id, 1128 ) 1129 items = apis_utils.execute_concurrently_with_pagination( 1130 api=gce_api, 1131 requests=requests, 1132 next_function=gce_api.regionInstanceGroupManagers().list_next, 1133 context=context, 1134 log_text=(f'listing regional managed instance groups of project {context.project_id}'), 1135 ) 1136 for i in items: 1137 result = re.match( 1138 r'https://www.googleapis.com/compute/v1/projects/[^/]+/(?:regions)/([^/]+)/', 1139 i['selfLink'], 1140 ) 1141 if not result: 1142 logging.error("mig %s selfLink didn't match regexp: %s", i['name'], i['selfLink']) 1143 continue 1144 location = result.group(1) 1145 labels = i.get('labels', {}) 1146 name = i.get('name', '') 1147 if not context.match_project_resource(location=location, labels=labels, resource=name): 1148 continue 1149 migs[i['id']] = ManagedInstanceGroup(project_id=context.project_id, resource_data=i) 1150 return migs
Get a list of regional ManagedInstanceGroups matching the given context, indexed by mig id.
1153@caching.cached_api_call 1154def get_instance_templates(project_id: str) -> Mapping[str, InstanceTemplate]: 1155 logging.info('fetching instance templates') 1156 templates = {} 1157 gce_api = apis.get_api('compute', 'v1', project_id) 1158 request = gce_api.instanceTemplates().list( 1159 project=project_id, 1160 returnPartialSuccess=True, 1161 # Fetch only a subset of the fields to improve performance. 1162 fields=( 1163 'items/name, items/properties/tags,' 1164 ' items/properties/networkInterfaces,' 1165 ' items/properties/serviceAccounts, items/properties/metadata' 1166 ), 1167 ) 1168 for t in apis_utils.list_all(request, next_function=gce_api.instanceTemplates().list_next): 1169 instance_template = InstanceTemplate(project_id, t) 1170 templates[instance_template.full_path] = instance_template 1171 return templates
1174@caching.cached_api_call 1175def get_project_metadata(project_id) -> Mapping[str, str]: 1176 gce_api = apis.get_api('compute', 'v1', project_id) 1177 logging.debug('fetching metadata of project %s\n', project_id) 1178 query = gce_api.projects().get(project=project_id) 1179 try: 1180 response = query.execute(num_retries=config.API_RETRIES) 1181 except googleapiclient.errors.HttpError as err: 1182 raise utils.GcpApiError(err) from err 1183 1184 mapped_metadata: Dict[str, str] = {} 1185 metadata = response.get('commonInstanceMetadata') 1186 if metadata and 'items' in metadata: 1187 for m_item in metadata['items']: 1188 mapped_metadata[m_item.get('key')] = m_item.get('value') 1189 return mapped_metadata
1192@caching.cached_api_call 1193def get_instances_serial_port_output(context: models.Context): 1194 """Get a list of serial port output for instances 1195 1196 which matches the given context, running and is not 1197 exported to cloud logging. 1198 """ 1199 # Create temp storage (diskcache.Deque) for output 1200 deque = caching.get_tmp_deque('tmp-gce-serial-output-') 1201 if not apis.is_enabled(context.project_id, 'compute'): 1202 return deque 1203 gce_api = apis.get_api('compute', 'v1', context.project_id) 1204 1205 # Serial port output are rolled over on day 7 and limited to 1MB. 1206 # Fetching serial outputs are very expensive so optimize to fetch. 1207 # Only relevant instances as storage size can grow drastically for 1208 # massive projects. Think 1MB * N where N is some large number. 1209 requests = [ 1210 gce_api.instances().getSerialPortOutput( 1211 project=i.project_id, 1212 zone=i.zone, 1213 instance=i.id, 1214 # To get all 1mb output 1215 start=-1000000, 1216 ) 1217 for i in get_instances(context).values() 1218 # fetch running instances that do not export to cloud logging 1219 if not i.is_serial_port_logging_enabled() and i.is_running 1220 ] 1221 requests_start_time = datetime.now() 1222 # Note: We are limited to 1000 calls in a single batch request. 1223 # We have to use multiple batch requests in batches of 1000 1224 # https://github.com/googleapis/google-api-python-client/blob/main/docs/batch.md 1225 batch_size = 1000 1226 for i in range(0, len(requests), batch_size): 1227 batch_requests = requests[i : i + batch_size] 1228 for _, response, exception in apis_utils.execute_concurrently( 1229 api=gce_api, requests=batch_requests, context=context 1230 ): 1231 if exception: 1232 if isinstance(exception, googleapiclient.errors.HttpError): 1233 raise utils.GcpApiError(exception) from exception 1234 else: 1235 raise exception 1236 1237 if response: 1238 result = re.match( 1239 r'https://www.googleapis.com/compute/v1/projects/([^/]+)/zones/[^/]+/instances/([^/]+)', 1240 response['selfLink'], 1241 ) 1242 if not result: 1243 logging.error("instance selfLink didn't match regexp: %s", response['selfLink']) 1244 return 1245 1246 project_id = result.group(1) 1247 instance_id = result.group(2) 1248 deque.appendleft( 1249 SerialPortOutput( 1250 project_id=project_id, 1251 instance_id=instance_id, 1252 contents=response['contents'].splitlines(), 1253 ) 1254 ) 1255 requests_end_time = datetime.now() 1256 logging.debug( 1257 'total serial logs processing time: %s, number of instances: %s', 1258 requests_end_time - requests_start_time, 1259 len(requests), 1260 ) 1261 return deque
Get a list of serial port output for instances
which matches the given context, running and is not exported to cloud logging.
1264@caching.cached_api_call 1265def get_instance_serial_port_output(project_id, zone, instance_name) -> Optional[SerialPortOutput]: 1266 """Get a list of serial port output for instances 1267 1268 which matches the given context, running and is not 1269 exported to cloud logging. 1270 """ 1271 # Create temp storage (diskcache.Deque) for output 1272 if not apis.is_enabled(project_id, 'compute'): 1273 return None 1274 gce_api = apis.get_api('compute', 'v1', project_id) 1275 1276 request = gce_api.instances().getSerialPortOutput( 1277 project=project_id, 1278 zone=zone, 1279 instance=instance_name, 1280 # To get all 1mb output 1281 start=-1000000, 1282 ) 1283 try: 1284 response = request.execute(num_retries=config.API_RETRIES) 1285 except googleapiclient.errors.HttpError: 1286 return None 1287 1288 if response: 1289 result = re.match( 1290 r'https://www.googleapis.com/compute/v1/projects/([^/]+)/zones/[^/]+/instances/([^/]+)', 1291 response['selfLink'], 1292 ) 1293 if not result: 1294 logging.error("instance selfLink didn't match regexp: %s", response['selfLink']) 1295 return None 1296 1297 project_id = result.group(1) 1298 instance_id = result.group(2) 1299 return SerialPortOutput( 1300 project_id, 1301 instance_id=instance_id, 1302 contents=response['contents'].splitlines(), 1303 ) 1304 return None
Get a list of serial port output for instances
which matches the given context, running and is not exported to cloud logging.
1307class Region(models.Resource): 1308 """Represents a GCE Region.""" 1309 1310 _resource_data: dict 1311 1312 def __init__(self, project_id, resource_data): 1313 super().__init__(project_id=project_id) 1314 self._resource_data = resource_data 1315 1316 @property 1317 def self_link(self) -> str: 1318 return self._resource_data['selfLink'] 1319 1320 @property 1321 def full_path(self) -> str: 1322 result = re.match(r'https://www.googleapis.com/compute/v1/(.*)', self.self_link) 1323 if result: 1324 return result.group(1) 1325 else: 1326 return f'>> {self.self_link}' 1327 1328 @property 1329 def name(self) -> str: 1330 return self._resource_data['name']
Represents a GCE Region.
1320 @property 1321 def full_path(self) -> str: 1322 result = re.match(r'https://www.googleapis.com/compute/v1/(.*)', self.self_link) 1323 if result: 1324 return result.group(1) 1325 else: 1326 return f'>> {self.self_link}'
Returns the full path of this resource.
Example: 'projects/gcpdiag-gke-1-9b90/zones/europe-west4-a/clusters/gke1'
1333@caching.cached_api_call 1334def get_all_regions(project_id: str) -> Iterable[Region]: 1335 """Return list of all regions 1336 1337 Args: 1338 project_id (str): project id for this request 1339 1340 Raises: 1341 utils.GcpApiError: Raises GcpApiError in case of query issues 1342 1343 Returns: 1344 Iterable[Region]: Return list of all regions 1345 """ 1346 try: 1347 gce_api = apis.get_api('compute', 'v1', project_id) 1348 request = gce_api.regions().list(project=project_id) 1349 response = request.execute(num_retries=config.API_RETRIES) 1350 if not response or 'items' not in response: 1351 return set() 1352 1353 return {Region(project_id, item) for item in response['items'] if 'name' in item} 1354 except googleapiclient.errors.HttpError as err: 1355 raise utils.GcpApiError(err) from err
Return list of all regions
Arguments:
- project_id (str): project id for this request
Raises:
- utils.GcpApiError: Raises GcpApiError in case of query issues
Returns:
Iterable[Region]: Return list of all regions
1358def get_regions_with_instances(context: models.Context) -> Iterable[Region]: 1359 """Return list of regions with instances 1360 1361 Args: 1362 context (models.Context): context for this request 1363 1364 Returns: 1365 Iterable[Region]: Return list of regions which contains instances 1366 """ 1367 1368 regions_of_instances = {i.region for i in get_instances(context).values()} 1369 1370 all_regions = get_all_regions(context.project_id) 1371 if not all_regions: 1372 return set() 1373 1374 return {r for r in all_regions if r.name in regions_of_instances}
Return list of regions with instances
Arguments:
- context (models.Context): context for this request
Returns:
Iterable[Region]: Return list of regions which contains instances
1377@caching.cached_api_call 1378def get_all_disks(context: models.Context) -> Iterable[Disk]: 1379 """Get all disks in a project, matching the context. 1380 1381 Args: 1382 context: The project context. 1383 1384 Returns: 1385 An iterable of Disk objects. 1386 """ 1387 project_id = context.project_id 1388 # Fetching only Zonal Disks(Regional disks exempted) 1389 try: 1390 gce_api = apis.get_api('compute', 'v1', project_id) 1391 requests = [ 1392 gce_api.disks().list(project=project_id, zone=zone) for zone in get_gce_zones(project_id) 1393 ] 1394 1395 logging.debug('listing gce disks of project %s', project_id) 1396 1397 items = apis_utils.execute_concurrently_with_pagination( 1398 api=gce_api, 1399 requests=requests, 1400 next_function=gce_api.disks().list_next, 1401 context=context, 1402 log_text=f'listing GCE disks of project {project_id}', 1403 ) 1404 1405 return {Disk(project_id, item) for item in items} 1406 1407 except googleapiclient.errors.HttpError as err: 1408 raise utils.GcpApiError(err) from err
Get all disks in a project, matching the context.
Arguments:
- context: The project context.
Returns:
An iterable of Disk objects.
1411@caching.cached_api_call 1412def get_all_disks_of_instance(context: models.Context, zone: str, instance_name: str) -> dict: 1413 """Get all disks of a given instance. 1414 1415 Args: 1416 context: The project context. 1417 zone: The zone of the instance. 1418 instance_name: The name of the instance. 1419 1420 Returns: 1421 A dict of Disk objects keyed by disk name. 1422 """ 1423 project_id = context.project_id 1424 # Fetching only Zonal Disks(Regional disks exempted) attached to an instance 1425 try: 1426 gce_api = apis.get_api('compute', 'v1', project_id) 1427 requests = [gce_api.disks().list(project=project_id, zone=zone)] 1428 logging.debug( 1429 'listing gce disks attached to instance %s in project %s', 1430 instance_name, 1431 project_id, 1432 ) 1433 1434 items = apis_utils.execute_concurrently_with_pagination( 1435 api=gce_api, 1436 requests=requests, 1437 next_function=gce_api.disks().list_next, 1438 context=context, 1439 log_text=(f'listing gce disks attached to instance {instance_name} in project {project_id}'), 1440 ) 1441 all_disk_list = {Disk(project_id, item) for item in items} 1442 disk_list = {} 1443 for disk in all_disk_list: 1444 if disk.users == [instance_name]: 1445 disk_list[disk.name] = disk 1446 return disk_list 1447 1448 except googleapiclient.errors.HttpError as err: 1449 raise utils.GcpApiError(err) from err
Get all disks of a given instance.
Arguments:
- context: The project context.
- zone: The zone of the instance.
- instance_name: The name of the instance.
Returns:
A dict of Disk objects keyed by disk name.
1452class InstanceEffectiveFirewalls(network_q.EffectiveFirewalls): 1453 """Effective firewall rules for a network interface on a VM instance. 1454 1455 Includes org/folder firewall policies). 1456 """ 1457 1458 _instance: Instance 1459 _nic: str 1460 1461 def __init__(self, instance, nic, resource_data): 1462 super().__init__(resource_data) 1463 self._instance = instance 1464 self._nic = nic
Effective firewall rules for a network interface on a VM instance.
Includes org/folder firewall policies).
1467@caching.cached_api_call(in_memory=True) 1468def get_instance_interface_effective_firewalls( 1469 instance: Instance, nic: str 1470) -> InstanceEffectiveFirewalls: 1471 """Return effective firewalls for a network interface on the instance.""" 1472 compute = apis.get_api('compute', 'v1', instance.project_id) 1473 request = compute.instances().getEffectiveFirewalls( 1474 project=instance.project_id, 1475 zone=instance.zone, 1476 instance=instance.name, 1477 networkInterface=nic, 1478 ) 1479 response = request.execute(num_retries=config.API_RETRIES) 1480 return InstanceEffectiveFirewalls(Instance, nic, response)
Return effective firewalls for a network interface on the instance.
1483def is_project_serial_port_logging_enabled(project_id: str) -> bool: 1484 if not apis.is_enabled(project_id, 'compute'): 1485 return False 1486 1487 value = get_project_metadata(project_id=project_id).get('serial-port-logging-enable') 1488 return bool(value and value.upper() in POSITIVE_BOOL_VALUES)
1507class SerialOutputQuery: 1508 """A serial output job that was started with prefetch_logs().""" 1509 1510 job: _SerialOutputJob 1511 1512 def __init__(self, job): 1513 self.job = job 1514 1515 @property 1516 def entries(self) -> Sequence: 1517 if not self.job.future: 1518 raise RuntimeError( 1519 "Fetching serial logs wasn't executed. did you call execute_get_serial_port_output()?" 1520 ) 1521 elif self.job.future.running(): 1522 logging.debug( 1523 'waiting for serial output results for project: %s', 1524 self.job.context.project_id, 1525 ) 1526 return self.job.future.result()
A serial output job that was started with prefetch_logs().
1515 @property 1516 def entries(self) -> Sequence: 1517 if not self.job.future: 1518 raise RuntimeError( 1519 "Fetching serial logs wasn't executed. did you call execute_get_serial_port_output()?" 1520 ) 1521 elif self.job.future.running(): 1522 logging.debug( 1523 'waiting for serial output results for project: %s', 1524 self.job.context.project_id, 1525 ) 1526 return self.job.future.result()
1532def execute_fetch_serial_port_outputs( 1533 query_executor: executor.ContextAwareExecutor, 1534): 1535 # start a thread to fetch serial log; processing logs can be large 1536 # depending on he number of instances in the project which aren't 1537 # logging to cloud logging. currently expects only one job but 1538 # implementing it so support for multiple projects is possible. 1539 global jobs_todo 1540 jobs_executing = jobs_todo 1541 jobs_todo = {} 1542 # query_executor = get_executor(context) 1543 for job in jobs_executing.values(): 1544 job.future = query_executor.submit(get_instances_serial_port_output, job.context)
1554class HealthCheck(models.Resource): 1555 """A Health Check resource.""" 1556 1557 _resource_data: dict 1558 _type: str 1559 1560 def __init__(self, project_id, resource_data): 1561 super().__init__(project_id=project_id) 1562 self._resource_data = resource_data 1563 1564 @property 1565 def name(self) -> str: 1566 return self._resource_data['name'] 1567 1568 @property 1569 def full_path(self) -> str: 1570 result = re.match(r'https://www.googleapis.com/compute/v1/(.*)', self.self_link) 1571 if result: 1572 return result.group(1) 1573 else: 1574 return f'>> {self.self_link}' 1575 1576 @property 1577 def short_path(self) -> str: 1578 path = self.project_id + '/' + self.name 1579 return path 1580 1581 @property 1582 def self_link(self) -> str: 1583 return self._resource_data['selfLink'] 1584 1585 @property 1586 def is_log_enabled(self) -> bool: 1587 try: 1588 log_config = self._resource_data.get('logConfig', False) 1589 if log_config and log_config['enable']: 1590 return True 1591 except KeyError: 1592 return False 1593 return False 1594 1595 @property 1596 def region(self): 1597 url = self._resource_data.get('region', '') 1598 match = re.search(r'/([^/]+)/?$', url) 1599 if match: 1600 region = match.group(1) 1601 return region 1602 return None 1603 1604 @property 1605 def type(self) -> str: 1606 return self._resource_data['type'] 1607 1608 @property 1609 def request_path(self) -> str: 1610 return self.get_health_check_property('requestPath', '/') 1611 1612 @property 1613 def request(self) -> str: 1614 return self.get_health_check_property('request') 1615 1616 @property 1617 def response(self) -> str: 1618 return self.get_health_check_property('response') 1619 1620 @property 1621 def port(self) -> int: 1622 return self.get_health_check_property('port') 1623 1624 @property 1625 def port_specification(self) -> str: 1626 return self.get_health_check_property('portSpecification', 'USE_FIXED_PORT') 1627 1628 @property 1629 def timeout_sec(self) -> int: 1630 return self._resource_data.get('timeoutSec', 5) 1631 1632 @property 1633 def check_interval_sec(self) -> int: 1634 return self._resource_data.get('checkIntervalSec', 5) 1635 1636 @property 1637 def unhealthy_threshold(self) -> int: 1638 return self._resource_data.get('unhealthyThreshold', 2) 1639 1640 @property 1641 def healthy_threshold(self) -> int: 1642 return self._resource_data.get('healthyThreshold', 2) 1643 1644 def get_health_check_property(self, property_name: str, default_value=None): 1645 health_check_types = { 1646 'HTTP': 'httpHealthCheck', 1647 'HTTPS': 'httpsHealthCheck', 1648 'HTTP2': 'http2HealthCheck', 1649 'TCP': 'tcpHealthCheck', 1650 'SSL': 'sslHealthCheck', 1651 'GRPC': 'grpcHealthCheck', 1652 } 1653 if self.type in health_check_types: 1654 health_check_data = self._resource_data.get(health_check_types[self.type]) 1655 if health_check_data: 1656 return health_check_data.get(property_name) or default_value 1657 return default_value
A Health Check resource.
1568 @property 1569 def full_path(self) -> str: 1570 result = re.match(r'https://www.googleapis.com/compute/v1/(.*)', self.self_link) 1571 if result: 1572 return result.group(1) 1573 else: 1574 return f'>> {self.self_link}'
Returns the full path of this resource.
Example: 'projects/gcpdiag-gke-1-9b90/zones/europe-west4-a/clusters/gke1'
1576 @property 1577 def short_path(self) -> str: 1578 path = self.project_id + '/' + self.name 1579 return path
Returns the short name for this resource.
Note that it isn't clear from this name what kind of resource it is.
Example: 'gke1'
1644 def get_health_check_property(self, property_name: str, default_value=None): 1645 health_check_types = { 1646 'HTTP': 'httpHealthCheck', 1647 'HTTPS': 'httpsHealthCheck', 1648 'HTTP2': 'http2HealthCheck', 1649 'TCP': 'tcpHealthCheck', 1650 'SSL': 'sslHealthCheck', 1651 'GRPC': 'grpcHealthCheck', 1652 } 1653 if self.type in health_check_types: 1654 health_check_data = self._resource_data.get(health_check_types[self.type]) 1655 if health_check_data: 1656 return health_check_data.get(property_name) or default_value 1657 return default_value
1660@caching.cached_api_call(in_memory=True) 1661def get_health_check(project_id: str, health_check: str, region: str = None) -> object: 1662 compute = apis.get_api('compute', 'v1', project_id) 1663 if not region or region == 'global': 1664 request = compute.healthChecks().get(project=project_id, healthCheck=health_check) 1665 else: 1666 request = compute.regionHealthChecks().get( 1667 project=project_id, healthCheck=health_check, region=region 1668 ) 1669 response = request.execute(num_retries=config.API_RETRIES) 1670 return HealthCheck(project_id, response)
1673class NetworkEndpointGroup(models.Resource): 1674 """A Network Endpoint Group resource.""" 1675 1676 _resource_data: dict 1677 _type: str 1678 1679 def __init__(self, project_id, resource_data): 1680 super().__init__(project_id=project_id) 1681 self._resource_data = resource_data 1682 1683 @property 1684 def name(self) -> str: 1685 return self._resource_data['name'] 1686 1687 @property 1688 def id(self) -> str: 1689 return self._resource_data['id'] 1690 1691 @property 1692 def full_path(self) -> str: 1693 result = re.match(r'https://www.googleapis.com/compute/v1/(.*)', self.self_link) 1694 if result: 1695 return result.group(1) 1696 else: 1697 return f'>> {self.self_link}' 1698 1699 @property 1700 def short_path(self) -> str: 1701 path = self.project_id + '/' + self.name 1702 return path 1703 1704 @property 1705 def self_link(self) -> str: 1706 return self._resource_data['selfLink']
A Network Endpoint Group resource.
1691 @property 1692 def full_path(self) -> str: 1693 result = re.match(r'https://www.googleapis.com/compute/v1/(.*)', self.self_link) 1694 if result: 1695 return result.group(1) 1696 else: 1697 return f'>> {self.self_link}'
Returns the full path of this resource.
Example: 'projects/gcpdiag-gke-1-9b90/zones/europe-west4-a/clusters/gke1'
1709@caching.cached_api_call(in_memory=True) 1710def get_zonal_network_endpoint_groups( 1711 context: models.Context, 1712) -> Mapping[str, NetworkEndpointGroup]: 1713 """Returns a list of Network Endpoint Groups in the project.""" 1714 groups: Dict[str, NetworkEndpointGroup] = {} 1715 if not apis.is_enabled(context.project_id, 'compute'): 1716 return groups 1717 gce_api = apis.get_api('compute', 'v1', context.project_id) 1718 requests = [ 1719 gce_api.networkEndpointGroups().list(project=context.project_id, zone=zone) 1720 for zone in get_gce_zones(context.project_id) 1721 ] 1722 logging.debug('listing gce networkEndpointGroups of project %s', context.project_id) 1723 items = apis_utils.execute_concurrently_with_pagination( 1724 api=gce_api, 1725 requests=requests, 1726 next_function=gce_api.networkEndpointGroups().list_next, 1727 context=context, 1728 log_text=(f'listing gce networkEndpointGroups of project {context.project_id}'), 1729 ) 1730 1731 for i in items: 1732 result = re.match( 1733 r'https://www.googleapis.com/compute/v1/projects/[^/]+/zones/([^/]+)', 1734 i['selfLink'], 1735 ) 1736 if not result: 1737 logging.error("instance %s selfLink didn't match regexp: %s", i['id'], i['selfLink']) 1738 continue 1739 zone = result.group(1) 1740 labels = i.get('labels', {}) 1741 resource = i.get('name', '') 1742 if not context.match_project_resource(location=zone, labels=labels, resource=resource): 1743 continue 1744 data = NetworkEndpointGroup(context.project_id, i) 1745 groups[data.full_path] = data 1746 return groups
Returns a list of Network Endpoint Groups in the project.
1749class TargetVpnGateway(models.Resource): 1750 """Represents a GCE Target VPN Gateway (Classic VPN).""" 1751 1752 _resource_data: dict 1753 1754 def __init__(self, project_id, resource_data): 1755 super().__init__(project_id=project_id) 1756 self._resource_data = resource_data 1757 1758 @property 1759 def id(self) -> str: 1760 return self._resource_data['id'] 1761 1762 @property 1763 def name(self) -> str: 1764 return self._resource_data['name'] 1765 1766 @property 1767 def self_link(self) -> str: 1768 return self._resource_data['selfLink'] 1769 1770 @property 1771 def full_path(self) -> str: 1772 result = re.match( 1773 r'https://www.googleapis.com/compute/v1/(.*)', 1774 self._resource_data['selfLink'], 1775 ) 1776 if result: 1777 return result.group(1) 1778 else: 1779 return '>> ' + self._resource_data['selfLink'] 1780 1781 @property 1782 def short_path(self) -> str: 1783 return self.project_id + '/' + self.name 1784 1785 @property 1786 def region(self) -> str: 1787 m = re.search(r'/regions/([^/]+)$', self._resource_data['region']) 1788 if not m: 1789 raise RuntimeError( 1790 "can't determine region of target VPN gateway %s (%s)" 1791 % (self.name, self._resource_data['region']) 1792 ) 1793 return m.group(1)
Represents a GCE Target VPN Gateway (Classic VPN).
1770 @property 1771 def full_path(self) -> str: 1772 result = re.match( 1773 r'https://www.googleapis.com/compute/v1/(.*)', 1774 self._resource_data['selfLink'], 1775 ) 1776 if result: 1777 return result.group(1) 1778 else: 1779 return '>> ' + self._resource_data['selfLink']
Returns the full path of this resource.
Example: 'projects/gcpdiag-gke-1-9b90/zones/europe-west4-a/clusters/gke1'
Returns the short name for this resource.
Note that it isn't clear from this name what kind of resource it is.
Example: 'gke1'
1796@caching.cached_api_call(in_memory=True) 1797def get_target_vpn_gateways( 1798 context: models.Context, 1799) -> Mapping[str, TargetVpnGateway]: 1800 """Get a list of Classic VPN Target VPN Gateways matching the given context.""" 1801 1802 gateways: Dict[str, TargetVpnGateway] = {} 1803 if not apis.is_enabled(context.project_id, 'compute'): 1804 return gateways 1805 gce_api = apis.get_api('compute', 'v1', context.project_id) 1806 request = gce_api.targetVpnGateways().aggregatedList( 1807 project=context.project_id, returnPartialSuccess=True 1808 ) 1809 logging.debug('listing GCE Target VPN Gateways of project %s', context.project_id) 1810 while request: # Continue as long as there are pages 1811 try: 1812 response = request.execute(num_retries=config.API_RETRIES) 1813 gateways_by_regions = response.get('items', {}) 1814 for _, data_ in gateways_by_regions.items(): 1815 if 'targetVpnGateways' not in data_: 1816 continue 1817 for gateway in data_['targetVpnGateways']: 1818 m = re.search(r'/regions/([^/]+)$', gateway['region']) 1819 if not m: 1820 continue 1821 region = m.group(1) 1822 if not context.match_project_resource( 1823 resource=gateway.get('name'), location=region 1824 ) and not context.match_project_resource(resource=gateway.get('id'), location=region): 1825 continue 1826 gateways[gateway['selfLink']] = TargetVpnGateway(context.project_id, gateway) 1827 request = gce_api.targetVpnGateways().aggregatedList_next( 1828 previous_request=request, previous_response=response 1829 ) 1830 except googleapiclient.errors.HttpError as err: 1831 raise utils.GcpApiError(err) from err 1832 return gateways
Get a list of Classic VPN Target VPN Gateways matching the given context.