Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/c7n_gcp/actions/labels.py: 60%
93 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
4from datetime import datetime, timedelta
5from dateutil import tz as tzutil
7from googleapiclient.errors import HttpError
9from c7n.utils import type_schema
10from c7n.filters import FilterValidationError
11from c7n.filters.offhours import Time
12from c7n.lookup import Lookup
14from c7n_gcp.actions import MethodAction
15from c7n_gcp.filters.labels import LabelActionFilter
16from c7n_gcp.provider import resources as gcp_resources
19class BaseLabelAction(MethodAction):
21 method_spec = {}
22 method_perm = 'update'
24 def get_labels_to_add(self, resource):
25 return None
27 def get_labels_to_delete(self, resource):
28 return None
30 def _merge_labels(self, current_labels, new_labels, remove_labels):
31 result = dict(current_labels)
32 if new_labels:
33 result.update(new_labels)
34 if remove_labels:
35 result = {k: v for k, v in result.items() if k not in remove_labels}
36 return result
38 def get_operation_name(self, model, resource):
39 return model.labels_op
41 def get_resource_params(self, model, resource):
42 current_labels = self._get_current_labels(resource)
43 new_labels = self.get_labels_to_add(resource)
44 remove_labels = self.get_labels_to_delete(resource)
45 all_labels = self._merge_labels(current_labels, new_labels, remove_labels)
47 return model.get_label_params(resource, all_labels)
49 def _get_current_labels(self, resource):
50 return resource.get('labels', {})
52 def handle_resource_error(self, client, model, resource, op_name, params, error):
53 if 'fingerprint' not in error.reason or not model.refresh:
54 return
55 try:
56 resource = model.refresh(client, resource)
57 params['body']['labelFingerprint'] = resource['labelFingerprint']
58 return self.invoke_api(client, op_name, params)
59 except HttpError as e:
60 if e.resp.status in self.ignore_error_codes:
61 return e
62 raise
64 @classmethod
65 def register_resources(cls, registry, resource_class):
66 if resource_class.resource_type.labels:
67 resource_class.action_registry.register('set-labels', SetLabelsAction)
68 resource_class.action_registry.register('mark-for-op', LabelDelayedAction)
70 resource_class.filter_registry.register('marked-for-op', LabelActionFilter)
73gcp_resources.subscribe(BaseLabelAction.register_resources)
76class SetLabelsAction(BaseLabelAction):
77 """Set labels to GCP resources
79 :example:
81 This policy will label all existing resource groups with a value such as environment
83 .. code-block:: yaml
85 policies:
86 - name: gcp-add-multiple-labels
87 resource: gcp.instance
88 description: |
89 Label all existing instances with multiple labels
90 actions:
91 - type: set-labels
92 labels:
93 environment: test
94 env_type: customer
96 - name: gcp-add-label-from-resource-attr
97 resource: gcp.instance
98 description: |
99 Label all existing instances with label taken from resource attribute
100 actions:
101 - type: set-labels
102 labels:
103 environment:
104 type: resource
105 key: name
106 default-value: name_not_found
108 - name: gcp-remove-label
109 resource: gcp.instance
110 description: |
111 Remove label from all instances
112 actions:
113 - type: set-labels
114 remove: [env]
116 """
118 schema = type_schema(
119 'set-labels',
120 labels={'type': 'object', "additionalProperties": Lookup.lookup_type({'type': 'string'})},
121 remove={'type': 'array', 'items': {'type': 'string'}})
123 def validate(self):
124 if not self.data.get('labels') and not self.data.get('remove'):
125 raise FilterValidationError("Must specify one of labels or remove")
127 def get_labels_to_add(self, resource):
128 return {k: Lookup.extract(v, resource) for k, v in self.data.get('labels', {}).items()}
130 def get_labels_to_delete(self, resource):
131 return self.data.get('remove')
134DEFAULT_TAG = "custodian_status"
137class LabelDelayedAction(BaseLabelAction):
138 """Label resources for future action.
140 The optional 'tz' parameter can be used to adjust the clock to align
141 with a given timezone. The default value is 'utc'.
143 If neither 'days' nor 'hours' is specified, Cloud Custodian will default
144 to marking the resource for action 4 days in the future.
146 :example:
148 .. code-block :: yaml
150 policies:
151 - name: vm-mark-for-stop
152 resource: gcp.instance
153 filters:
154 - type: value
155 key: name
156 value: instance-to-stop-in-four-days
157 actions:
158 - type: mark-for-op
159 op: stop
160 days: 2
161 """
163 schema = type_schema(
164 'mark-for-op',
165 label={'type': 'string'},
166 msg={'type': 'string'},
167 days={'type': 'number', 'minimum': 0, 'exclusiveMinimum': False},
168 hours={'type': 'number', 'minimum': 0, 'exclusiveMinimum': False},
169 tz={'type': 'string'},
170 op={'type': 'string'}
171 )
173 default_template = 'resource_policy-{op}-{action_date}'
175 def __init__(self, data=None, manager=None, log_dir=None):
176 super(LabelDelayedAction, self).__init__(data, manager, log_dir)
177 self.tz = tzutil.gettz(
178 Time.TZ_ALIASES.get(self.data.get('tz', 'utc')))
180 msg_tmpl = self.data.get('msg', self.default_template)
182 op = self.data.get('op', 'stop')
183 days = self.data.get('days', 0)
184 hours = self.data.get('hours', 0)
185 action_date = self.generate_timestamp(days, hours)
187 self.label = self.data.get('label', DEFAULT_TAG)
188 self.msg = msg_tmpl.format(
189 op=op, action_date=action_date)
191 def validate(self):
192 op = self.data.get('op')
193 if self.manager and op not in self.manager.action_registry.keys():
194 raise FilterValidationError(
195 "mark-for-op specifies invalid op:%s in %s" % (
196 op, self.manager.data))
198 self.tz = tzutil.gettz(
199 Time.TZ_ALIASES.get(self.data.get('tz', 'utc')))
200 if not self.tz:
201 raise FilterValidationError(
202 "Invalid timezone specified %s in %s" % (
203 self.tz, self.manager.data))
205 def generate_timestamp(self, days, hours):
206 n = datetime.now(tz=self.tz)
207 if days is None or hours is None:
208 # maintains default value of days being 4 if nothing is provided
209 days = 4
210 action_date = (n + timedelta(days=days, hours=hours))
211 if hours > 0:
212 action_date_string = action_date.strftime('%Y_%m_%d__%H_%M')
213 else:
214 action_date_string = action_date.strftime('%Y_%m_%d__0_0')
216 return action_date_string
218 def get_labels_to_add(self, resource):
219 return {self.label: self.msg}