Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/c7n_gcp/resources/compute.py: 75%
322 statements
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-08 06:51 +0000
« prev ^ index » next coverage.py v7.3.2, created at 2023-12-08 06:51 +0000
1# Copyright The Cloud Custodian Authors.
2# SPDX-License-Identifier: Apache-2.0
4import re
6from datetime import datetime
8from c7n.utils import local_session, type_schema
10from c7n_gcp.actions import MethodAction
11from c7n_gcp.filters import IamPolicyFilter
12from c7n_gcp.filters.iampolicy import IamPolicyValueFilter
13from c7n_gcp.provider import resources
14from c7n_gcp.query import QueryResourceManager, TypeInfo, ChildResourceManager, ChildTypeInfo
16from c7n.filters.core import ValueFilter
17from c7n.filters.offhours import OffHour, OnHour
20@resources.register('instance')
21class Instance(QueryResourceManager):
23 class resource_type(TypeInfo):
24 service = 'compute'
25 version = 'v1'
26 component = 'instances'
27 enum_spec = ('aggregatedList', 'items.*.instances[]', None)
28 scope = 'project'
29 name = id = 'name'
30 labels = True
31 default_report_fields = ['name', 'status', 'creationTimestamp', 'machineType', 'zone']
32 asset_type = "compute.googleapis.com/Instance"
33 scc_type = "google.compute.Instance"
34 metric_key = 'metric.labels.instance_name'
35 urn_component = "instance"
36 urn_zonal = True
38 @staticmethod
39 def get(client, resource_info):
40 # The api docs for compute instance get are wrong,
41 # they spell instance as resourceId
42 return client.execute_command(
43 'get', {'project': resource_info['project_id'],
44 'zone': resource_info['zone'],
45 'instance': resource_info[
46 'resourceName'].rsplit('/', 1)[-1]})
48 @staticmethod
49 def get_label_params(resource, all_labels):
50 project, zone, instance = re.match(
51 '.*?/projects/(.*?)/zones/(.*?)/instances/(.*)',
52 resource['selfLink']).groups()
53 return {'project': project, 'zone': zone, 'instance': instance,
54 'body': {
55 'labels': all_labels,
56 'labelFingerprint': resource['labelFingerprint']
57 }}
59 @classmethod
60 def refresh(cls, client, resource):
61 project, zone, name = re.match(
62 '.*?/projects/(.*?)/zones/(.*?)/instances/(.*)',
63 resource['selfLink']).groups()
64 return cls.get(
65 client,
66 {
67 'project_id': project,
68 'zone': zone,
69 'resourceName': name
70 }
71 )
74Instance.filter_registry.register('offhour', OffHour)
75Instance.filter_registry.register('onhour', OnHour)
78@Instance.filter_registry.register('effective-firewall')
79class EffectiveFirewall(ValueFilter):
80 """Filters instances by their effective firewall rules.
81 See `getEffectiveFirewalls
82 <https://cloud.google.com/compute/docs/reference/rest/v1/instances/getEffectiveFirewalls>`_
83 for valid fields.
85 :example:
87 Filter all instances that have a firewall rule that allows public
88 acess
90 .. code-block:: yaml
92 policies:
93 - name: find-publicly-accessable-instances
94 resource: gcp.instance
95 filters:
96 - type: effective-firewall
97 key: firewalls[*].sourceRanges[]
98 op: contains
99 value: "0.0.0.0/0"
100 """
102 schema = type_schema('effective-firewall', rinherit=ValueFilter.schema)
103 permissions = ('compute.instances.getEffectiveFirewalls',)
105 def get_resource_params(self, resource):
106 path_param_re = re.compile('.*?/projects/(.*?)/zones/(.*?)/instances/.*')
107 project, zone = path_param_re.match(resource['selfLink']).groups()
108 return {'project': project, 'zone': zone, 'instance': resource["name"]}
110 def process_resource(self, client, resource):
111 params = self.get_resource_params(resource)
112 effective_firewalls = []
113 for interface in resource["networkInterfaces"]:
114 effective_firewalls.append(client.execute_command(
115 'getEffectiveFirewalls', {"networkInterface": interface["name"], **params}))
116 return super(EffectiveFirewall, self).process(effective_firewalls, None)
118 def get_client(self, session, model):
119 return session.client(
120 model.service, model.version, model.component)
122 def process(self, resources, event=None):
123 model = self.manager.get_model()
124 session = local_session(self.manager.session_factory)
125 client = self.get_client(session, model)
126 return [r for r in resources if self.process_resource(client, r)]
129class InstanceAction(MethodAction):
131 def get_resource_params(self, model, resource):
132 path_param_re = re.compile('.*?/projects/(.*?)/zones/(.*?)/instances/(.*)')
133 project, zone, instance = path_param_re.match(resource['selfLink']).groups()
134 return {'project': project, 'zone': zone, 'instance': instance}
137@Instance.action_registry.register('start')
138class Start(InstanceAction):
140 schema = type_schema('start')
141 method_spec = {'op': 'start'}
142 attr_filter = ('status', ('TERMINATED',))
145@Instance.action_registry.register('stop')
146class Stop(InstanceAction):
147 """
148 Caution: `stop` in GCP is closer to terminate in terms of effect.
150 `suspend` is closer to stop in other providers.
152 See https://cloud.google.com/compute/docs/instances/instance-life-cycle
153 """
155 schema = type_schema('stop')
156 method_spec = {'op': 'stop'}
157 attr_filter = ('status', ('RUNNING',))
160@Instance.action_registry.register('suspend')
161class Suspend(InstanceAction):
163 schema = type_schema('suspend')
164 method_spec = {'op': 'suspend'}
165 attr_filter = ('status', ('RUNNING',))
168@Instance.action_registry.register('resume')
169class Resume(InstanceAction):
171 schema = type_schema('resume')
172 method_spec = {'op': 'resume'}
173 attr_filter = ('status', ('SUSPENDED',))
176@Instance.action_registry.register('delete')
177class Delete(InstanceAction):
179 schema = type_schema('delete')
180 method_spec = {'op': 'delete'}
184@Instance.action_registry.register('detach-disks')
185class DetachDisks(MethodAction):
186 """
187 `Detaches <https://cloud.google.com/compute/docs/reference/rest/v1/instances/detachDisk>`_
188 all disks from instance. The action does not specify any parameters.
190 It may be useful to be used before deleting instances to not delete disks
191 that are set to auto delete.
193 :Example:
195 .. code-block:: yaml
197 policies:
198 - name: gcp-instance-detach-disks
199 resource: gcp.instance
200 filters:
201 - type: value
202 key: name
203 value: instance-template-to-detahc
204 actions:
205 - type: detach-disks
206 """
207 schema = type_schema('detach-disks')
208 attr_filter = ('status', ('TERMINATED',))
209 method_spec = {'op': 'detachDisk'}
210 path_param_re = re.compile(
211 '.*?/projects/(.*?)/zones/(.*?)/instances/(.*)')
213 def validate(self):
214 pass
216 def process_resource_set(self, client, model, resources):
217 for resource in resources:
218 self.process_resource(client, resource)
220 def process_resource(self, client, resource):
221 op_name = 'detachDisk'
223 project, zone, instance = self.path_param_re.match(
224 resource['selfLink']).groups()
226 base_params = {'project': project, 'zone': zone, 'instance': instance}
227 for disk in resource.get('disks', []):
228 params = dict(base_params, deviceName=disk['deviceName'])
229 self.invoke_api(client, op_name, params)
232@Instance.action_registry.register('create-machine-image')
233class CreateMachineImage(MethodAction):
234 """
235 `Creates <https://cloud.google.com/compute/docs/reference/rest/beta/machineImages/insert>`_
236 Machine Image from instance.
238 The `name_format` specifies name of image in python `format string <https://pyformat.info/>`
240 Inside format string there are defined variables:
241 - `now`: current time
242 - `instance`: whole instance resource
244 Default name format is `{instance[name]}`
246 :Example:
248 .. code-block:: yaml
250 policies:
251 - name: gcp-create-machine-image
252 resource: gcp.instance
253 filters:
254 - type: value
255 key: name
256 value: instance-create-to-make-image
257 actions:
258 - type: create-machine-image
259 name_format: "{instance[name]:.50}-{now:%Y-%m-%d}"
261 """
262 schema = type_schema('create-machine-image', name_format={'type': 'string'})
263 method_spec = {'op': 'insert'}
264 permissions = ('compute.machineImages.create',)
266 def get_resource_params(self, model, resource):
267 path_param_re = re.compile('.*?/projects/(.*?)/zones/(.*?)/instances/(.*)')
268 project, _, _ = path_param_re.match(resource['selfLink']).groups()
269 name_format = self.data.get('name_format', '{instance[name]}')
270 name = name_format.format(instance=resource, now=datetime.now())
272 return {'project': project, 'sourceInstance': resource['selfLink'], 'body': {'name': name}}
274 def get_client(self, session, model):
275 return session.client(model.service, "beta", "machineImages")
278@resources.register('image')
279class Image(QueryResourceManager):
281 class resource_type(TypeInfo):
282 service = 'compute'
283 version = 'v1'
284 component = 'images'
285 name = id = 'name'
286 default_report_fields = [
287 "name", "description", "sourceType", "status", "creationTimestamp",
288 "diskSizeGb", "family"]
289 asset_type = "compute.googleapis.com/Image"
290 urn_component = "image"
291 labels = True
293 @staticmethod
294 def get(client, resource_info):
295 return client.execute_command(
296 'get', {'project': resource_info['project_id'],
297 'image': resource_info['image_id']})
299 @staticmethod
300 def get_label_params(resource, all_labels):
301 project, resource_id = re.match(
302 '.*?/projects/(.*?)/global/images/(.*)',
303 resource['selfLink']).groups()
304 return {'project': project, 'resource': resource_id,
305 'body': {
306 'labels': all_labels,
307 'labelFingerprint': resource['labelFingerprint']
308 }}
310 @classmethod
311 def refresh(cls, client, resource):
312 project, resource_id = re.match(
313 '.*?/projects/(.*?)/global/images/(.*)',
314 resource['selfLink']).groups()
315 return cls.get(
316 client,
317 {
318 'project_id': project,
319 'image_id': resource_id
320 }
321 )
324@Image.filter_registry.register('iam-policy')
325class ImageIamPolicyFilter(IamPolicyFilter):
326 """
327 Overrides the base implementation to process images resources correctly.
328 """
329 permissions = ('compute.images.getIamPolicy',)
331 def _verb_arguments(self, resource):
332 project, _ = re.match(
333 '.*?/projects/(.*?)/global/images/(.*)',
334 resource['selfLink']).groups()
335 verb_arguments = {'resource': resource[self.manager.resource_type.id], 'project': project}
336 return verb_arguments
338 def process_resources(self, resources):
339 value_filter = IamPolicyValueFilter(self.data['doc'], self.manager)
340 value_filter._verb_arguments = self._verb_arguments
341 return value_filter.process(resources)
344@Image.action_registry.register('delete')
345class DeleteImage(MethodAction):
347 schema = type_schema('delete')
348 method_spec = {'op': 'delete'}
349 attr_filter = ('status', ('READY'))
350 path_param_re = re.compile('.*?/projects/(.*?)/global/images/(.*)')
352 def get_resource_params(self, m, r):
353 project, image_id = self.path_param_re.match(r['selfLink']).groups()
354 return {'project': project, 'image': image_id}
357@resources.register('disk')
358class Disk(QueryResourceManager):
360 class resource_type(TypeInfo):
361 service = 'compute'
362 version = 'v1'
363 component = 'disks'
364 scope = 'zone'
365 enum_spec = ('aggregatedList', 'items.*.disks[]', None)
366 name = id = 'name'
367 labels = True
368 default_report_fields = ["name", "sizeGb", "status", "zone"]
369 asset_type = "compute.googleapis.com/Disk"
370 urn_component = "disk"
371 urn_zonal = True
373 @staticmethod
374 def get(client, resource_info):
375 return client.execute_command(
376 'get', {'project': resource_info['project_id'],
377 'zone': resource_info['zone'],
378 'disk': resource_info['disk_id']})
380 @staticmethod
381 def get_label_params(resource, all_labels):
382 path_param_re = re.compile('.*?/projects/(.*?)/zones/(.*?)/disks/(.*)')
383 project, zone, instance = path_param_re.match(
384 resource['selfLink']).groups()
385 return {'project': project, 'zone': zone, 'resource': instance,
386 'body': {
387 'labels': all_labels,
388 'labelFingerprint': resource['labelFingerprint']
389 }}
392@Disk.action_registry.register('snapshot')
393class DiskSnapshot(MethodAction):
394 """
395 `Snapshots <https://cloud.google.com/compute/docs/reference/rest/v1/disks/createSnapshot>`_
396 disk.
398 The `name_format` specifies name of snapshot in python `format string <https://pyformat.info/>`
400 Inside format string there are defined variables:
401 - `now`: current time
402 - `disk`: whole disk resource
404 Default name format is `{disk.name}`
406 :Example:
408 .. code-block:: yaml
410 policies:
411 - name: gcp-disk-snapshot
412 resource: gcp.disk
413 filters:
414 - type: value
415 key: name
416 value: disk-7
417 actions:
418 - type: snapshot
419 name_format: "{disk[name]:.50}-{now:%Y-%m-%d}"
420 """
421 schema = type_schema('snapshot', name_format={'type': 'string'})
422 method_spec = {'op': 'createSnapshot'}
423 path_param_re = re.compile(
424 '.*?/projects/(.*?)/zones/(.*?)/disks/(.*)')
425 attr_filter = ('status', ('RUNNING', 'READY'))
427 def get_resource_params(self, model, resource):
428 project, zone, resourceId = self.path_param_re.match(resource['selfLink']).groups()
429 name_format = self.data.get('name_format', '{disk[name]}')
430 name = name_format.format(disk=resource, now=datetime.now())
432 return {
433 'project': project,
434 'zone': zone,
435 'disk': resourceId,
436 'body': {
437 'name': name,
438 'labels': resource.get('labels', {}),
439 }
440 }
443@Disk.action_registry.register('delete')
444class DiskDelete(MethodAction):
446 schema = type_schema('delete')
447 method_spec = {'op': 'delete'}
448 path_param_re = re.compile(
449 '.*?/projects/(.*?)/zones/(.*?)/disks/(.*)')
450 attr_filter = ('status', ('RUNNING', 'READY'))
452 def get_resource_params(self, m, r):
453 project, zone, resourceId = self.path_param_re.match(r['selfLink']).groups()
454 return {
455 'project': project,
456 'zone': zone,
457 'disk': resourceId,
458 }
461@resources.register('snapshot')
462class Snapshot(QueryResourceManager):
464 class resource_type(TypeInfo):
465 service = 'compute'
466 version = 'v1'
467 component = 'snapshots'
468 enum_spec = ('list', 'items[]', None)
469 name = id = 'name'
470 default_report_fields = ["name", "status", "diskSizeGb", "creationTimestamp"]
471 asset_type = "compute.googleapis.com/Snapshot"
472 urn_component = "snapshot"
474 @staticmethod
475 def get(client, resource_info):
476 return client.execute_command(
477 'get', {'project': resource_info['project_id'],
478 'snapshot': resource_info['snapshot_id']})
481@Snapshot.action_registry.register('delete')
482class DeleteSnapshot(MethodAction):
484 schema = type_schema('delete')
485 method_spec = {'op': 'delete'}
486 attr_filter = ('status', ('READY', 'UPLOADING'))
487 path_param_re = re.compile('.*?/projects/(.*?)/global/snapshots/(.*)')
489 def get_resource_params(self, m, r):
490 project, snapshot_id = self.path_param_re.match(r['selfLink']).groups()
491 # Docs are wrong :-(
492 # https://cloud.google.com/compute/docs/reference/rest/v1/snapshots/delete
493 return {'project': project, 'snapshot': snapshot_id}
496@resources.register('instance-template')
497class InstanceTemplate(QueryResourceManager):
498 """GCP resource: https://cloud.google.com/compute/docs/reference/rest/v1/instanceTemplates"""
499 class resource_type(TypeInfo):
500 service = 'compute'
501 version = 'v1'
502 component = 'instanceTemplates'
503 scope = 'zone'
504 enum_spec = ('list', 'items[]', None)
505 name = id = 'name'
506 default_report_fields = [
507 name, "description", "creationTimestamp",
508 "properties.machineType", "properties.description"]
509 asset_type = "compute.googleapis.com/InstanceTemplate"
510 urn_component = "instance-template"
512 @staticmethod
513 def get(client, resource_info):
514 return client.execute_command(
515 'get', {'project': resource_info['project_id'],
516 'instanceTemplate': resource_info['instance_template_name']})
519@InstanceTemplate.action_registry.register('delete')
520class InstanceTemplateDelete(MethodAction):
521 """
522 `Deletes <https://cloud.google.com/compute/docs/reference/rest/v1/instanceTemplates/delete>`_
523 an Instance Template. The action does not specify any parameters.
525 :Example:
527 .. code-block:: yaml
529 policies:
530 - name: gcp-instance-template-delete
531 resource: gcp.instance-template
532 filters:
533 - type: value
534 key: name
535 value: instance-template-to-delete
536 actions:
537 - type: delete
538 """
539 schema = type_schema('delete')
540 method_spec = {'op': 'delete'}
542 def get_resource_params(self, m, r):
543 project, instance_template = re.match('.*/projects/(.*?)/.*/instanceTemplates/(.*)',
544 r['selfLink']).groups()
545 return {'project': project,
546 'instanceTemplate': instance_template}
549@resources.register('autoscaler')
550class Autoscaler(QueryResourceManager):
551 """GCP resource: https://cloud.google.com/compute/docs/reference/rest/v1/autoscalers"""
552 class resource_type(TypeInfo):
553 service = 'compute'
554 version = 'v1'
555 component = 'autoscalers'
556 name = id = 'name'
557 enum_spec = ('aggregatedList', 'items.*.autoscalers[]', None)
558 default_report_fields = [
559 "name", "description", "status", "target", "recommendedSize"]
560 asset_type = "compute.googleapis.com/Autoscaler"
561 metric_key = "resource.labels.autoscaler_name"
562 urn_component = "autoscaler"
563 urn_zonal = True
565 @staticmethod
566 def get(client, resource_info):
567 project, zone, autoscaler = re.match(
568 'projects/(.*?)/zones/(.*?)/autoscalers/(.*)',
569 resource_info['resourceName']).groups()
571 return client.execute_command(
572 'get', {'project': project,
573 'zone': zone,
574 'autoscaler': autoscaler})
577@Autoscaler.action_registry.register('set')
578class AutoscalerSet(MethodAction):
579 """
580 `Patches <https://cloud.google.com/compute/docs/reference/rest/v1/autoscalers/patch>`_
581 configuration parameters for the autoscaling algorithm.
583 The `coolDownPeriodSec` specifies the number of seconds that the autoscaler
584 should wait before it starts collecting information from a new instance.
586 The `cpuUtilization.utilizationTarget` specifies the target CPU utilization that the
587 autoscaler should maintain.
589 The `loadBalancingUtilization.utilizationTarget` specifies fraction of backend capacity
590 utilization (set in HTTP(S) load balancing configuration) that autoscaler should maintain.
592 The `minNumReplicas` specifies the minimum number of replicas that the autoscaler can
593 scale down to.
595 The `maxNumReplicas` specifies the maximum number of instances that the autoscaler can
596 scale up to.
598 :Example:
600 .. code-block:: yaml
602 policies:
603 - name: gcp-autoscaler-set
604 resource: gcp.autoscaler
605 filters:
606 - type: value
607 key: name
608 value: instance-group-2
609 actions:
610 - type: set
611 coolDownPeriodSec: 20
612 cpuUtilization:
613 utilizationTarget: 0.7
614 loadBalancingUtilization:
615 utilizationTarget: 0.7
616 minNumReplicas: 1
617 maxNumReplicas: 4
618 """
619 schema = type_schema('set',
620 **{
621 'coolDownPeriodSec': {
622 'type': 'integer',
623 'minimum': 15
624 },
625 'cpuUtilization': {
626 'type': 'object',
627 'required': ['utilizationTarget'],
628 'properties': {
629 'utilizationTarget': {
630 'type': 'number',
631 'exclusiveMinimum': 0,
632 'maximum': 1
633 }
634 },
635 },
636 'loadBalancingUtilization': {
637 'type': 'object',
638 'required': ['utilizationTarget'],
639 'properties': {
640 'utilizationTarget': {
641 'type': 'number',
642 'exclusiveMinimum': 0,
643 'maximum': 1
644 }
645 }
646 },
647 'maxNumReplicas': {
648 'type': 'integer',
649 'exclusiveMinimum': 0
650 },
651 'minNumReplicas': {
652 'type': 'integer',
653 'exclusiveMinimum': 0
654 }
655 })
656 method_spec = {'op': 'patch'}
657 path_param_re = re.compile('.*?/projects/(.*?)/zones/(.*?)/autoscalers/(.*)')
658 method_perm = 'update'
660 def get_resource_params(self, model, resource):
661 project, zone, autoscaler = self.path_param_re.match(resource['selfLink']).groups()
662 body = {}
664 if 'coolDownPeriodSec' in self.data:
665 body['coolDownPeriodSec'] = self.data['coolDownPeriodSec']
667 if 'cpuUtilization' in self.data:
668 body['cpuUtilization'] = self.data['cpuUtilization']
670 if 'loadBalancingUtilization' in self.data:
671 body['loadBalancingUtilization'] = self.data['loadBalancingUtilization']
673 if 'maxNumReplicas' in self.data:
674 body['maxNumReplicas'] = self.data['maxNumReplicas']
676 if 'minNumReplicas' in self.data:
677 body['minNumReplicas'] = self.data['minNumReplicas']
679 result = {'project': project,
680 'zone': zone,
681 'autoscaler': autoscaler,
682 'body': {
683 'autoscalingPolicy': body
684 }}
686 return result
689@resources.register('zone')
690class Zone(QueryResourceManager):
691 """GC resource: https://cloud.google.com/compute/docs/reference/rest/v1/zones"""
692 class resource_type(TypeInfo):
693 service = 'compute'
694 version = 'v1'
695 component = 'zones'
696 enum_spec = ('list', 'items[]', None)
697 scope = 'project'
698 name = id = 'name'
699 default_report_fields = ['id', 'name', 'dnsName', 'creationTime', 'visibility']
700 asset_type = "compute.googleapis.com/compute"
701 scc_type = "google.cloud.dns.ManagedZone"
704@resources.register('compute-project')
705class Project(QueryResourceManager):
706 """GCP resource: https://cloud.google.com/compute/docs/reference/rest/v1/projects"""
707 class resource_type(TypeInfo):
708 service = 'compute'
709 version = 'v1'
710 component = 'projects'
711 enum_spec = ('get', '[@]', None)
712 name = id = 'name'
713 default_report_fields = ["name"]
714 asset_type = 'compute.googleapis.com/Project'
716 @staticmethod
717 def get(client, resource_info):
718 return client.execute_command(
719 'get', {'project': resource_info['project_id']})
722@resources.register('instance-group-manager')
723class InstanceGroupManager(ChildResourceManager):
725 class resource_type(ChildTypeInfo):
726 service = 'compute'
727 version = 'v1'
728 component = 'instanceGroupManagers'
729 enum_spec = ('list', 'items[]', None)
730 name = id = 'name'
731 parent_spec = {
732 'resource': 'zone',
733 'child_enum_params': {
734 ('name', 'zone')},
735 'use_child_query': False,
736 }
737 default_report_fields = ['id', 'name', 'dnsName', 'creationTime', 'visibility']