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

1# Copyright The Cloud Custodian Authors. 

2# SPDX-License-Identifier: Apache-2.0 

3 

4from datetime import datetime, timedelta 

5from dateutil import tz as tzutil 

6 

7from googleapiclient.errors import HttpError 

8 

9from c7n.utils import type_schema 

10from c7n.filters import FilterValidationError 

11from c7n.filters.offhours import Time 

12from c7n.lookup import Lookup 

13 

14from c7n_gcp.actions import MethodAction 

15from c7n_gcp.filters.labels import LabelActionFilter 

16from c7n_gcp.provider import resources as gcp_resources 

17 

18 

19class BaseLabelAction(MethodAction): 

20 

21 method_spec = {} 

22 method_perm = 'update' 

23 

24 def get_labels_to_add(self, resource): 

25 return None 

26 

27 def get_labels_to_delete(self, resource): 

28 return None 

29 

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 

37 

38 def get_operation_name(self, model, resource): 

39 return model.labels_op 

40 

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) 

46 

47 return model.get_label_params(resource, all_labels) 

48 

49 def _get_current_labels(self, resource): 

50 return resource.get('labels', {}) 

51 

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 

63 

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) 

69 

70 resource_class.filter_registry.register('marked-for-op', LabelActionFilter) 

71 

72 

73gcp_resources.subscribe(BaseLabelAction.register_resources) 

74 

75 

76class SetLabelsAction(BaseLabelAction): 

77 """Set labels to GCP resources 

78 

79 :example: 

80 

81 This policy will label all existing resource groups with a value such as environment 

82 

83 .. code-block:: yaml 

84 

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 

95 

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 

107 

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] 

115 

116 """ 

117 

118 schema = type_schema( 

119 'set-labels', 

120 labels={'type': 'object', "additionalProperties": Lookup.lookup_type({'type': 'string'})}, 

121 remove={'type': 'array', 'items': {'type': 'string'}}) 

122 

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") 

126 

127 def get_labels_to_add(self, resource): 

128 return {k: Lookup.extract(v, resource) for k, v in self.data.get('labels', {}).items()} 

129 

130 def get_labels_to_delete(self, resource): 

131 return self.data.get('remove') 

132 

133 

134DEFAULT_TAG = "custodian_status" 

135 

136 

137class LabelDelayedAction(BaseLabelAction): 

138 """Label resources for future action. 

139 

140 The optional 'tz' parameter can be used to adjust the clock to align 

141 with a given timezone. The default value is 'utc'. 

142 

143 If neither 'days' nor 'hours' is specified, Cloud Custodian will default 

144 to marking the resource for action 4 days in the future. 

145 

146 :example: 

147 

148 .. code-block :: yaml 

149 

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 """ 

162 

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 ) 

172 

173 default_template = 'resource_policy-{op}-{action_date}' 

174 

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'))) 

179 

180 msg_tmpl = self.data.get('msg', self.default_template) 

181 

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) 

186 

187 self.label = self.data.get('label', DEFAULT_TAG) 

188 self.msg = msg_tmpl.format( 

189 op=op, action_date=action_date) 

190 

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)) 

197 

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)) 

204 

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') 

215 

216 return action_date_string 

217 

218 def get_labels_to_add(self, resource): 

219 return {self.label: self.msg}