1# Copyright The Cloud Custodian Authors.
2# SPDX-License-Identifier: Apache-2.0
3
4import re
5
6from c7n.utils import type_schema, jmespath_search
7from c7n.filters.offhours import OffHour, OnHour
8from c7n_gcp.actions import MethodAction
9from c7n_gcp.provider import resources
10from c7n_gcp.query import (
11 QueryResourceManager, TypeInfo, ChildResourceManager, ChildTypeInfo
12)
13from datetime import datetime
14from dateutil.parser import parse
15
16
17@resources.register('sql-instance')
18class SqlInstance(QueryResourceManager):
19
20 class resource_type(TypeInfo):
21 service = 'sqladmin'
22 version = 'v1beta4'
23 component = 'instances'
24 enum_spec = ('list', 'items[]', None)
25 scope = 'project'
26 labels = True
27 labels_op = 'patch'
28 name = id = 'name'
29 default_report_fields = [
30 "name", "state", "databaseVersion", "settings.tier", "settings.dataDiskSizeGb"]
31 asset_type = "sqladmin.googleapis.com/Instance"
32 scc_type = "google.cloud.sql.Instance"
33 metric_key = 'resource.labels.database_id'
34 perm_service = 'cloudsql'
35 urn_component = "instance"
36
37 @staticmethod
38 def get(client, resource_info):
39 return client.execute_command(
40 'get', {'project': resource_info['project_id'],
41 'instance': resource_info['database_id'].rsplit(':', 1)[-1]})
42
43 @staticmethod
44 def get_metric_resource_name(resource):
45 return "{}:{}".format(resource["project"], resource["name"])
46
47 @staticmethod
48 def get_label_params(resource, all_labels):
49 path_param_re = re.compile('.*?/projects/(.*?)/instances/(.*)')
50 project, instance = path_param_re.match(
51 resource['selfLink']).groups()
52 return {
53 'project': project, 'instance': instance,
54 'body': {
55 'settings': {
56 'userLabels': all_labels
57 }
58 }
59 }
60
61 def augment(self, resources):
62 for r in resources:
63 if 'userLabels' in r['settings']:
64 r['labels'] = r['settings']['userLabels']
65 return resources
66
67
68SqlInstance.filter_registry.register('offhour', OffHour)
69SqlInstance.filter_registry.register('onhour', OnHour)
70
71
72class SqlInstanceAction(MethodAction):
73
74 def get_resource_params(self, model, resource):
75 project, instance = self.path_param_re.match(
76 resource['selfLink']).groups()
77 return {'project': project, 'instance': instance}
78
79
80@SqlInstance.action_registry.register('delete')
81class SqlInstanceDelete(SqlInstanceAction):
82
83 schema = type_schema('delete', force={'type': 'boolean'})
84 method_spec = {'op': 'delete'}
85 path_param_re = re.compile(
86 '.*?/projects/(.*?)/instances/(.*)')
87
88 def process(self, resources):
89 if self.data.get('force'):
90 self.disable_protection(resources)
91 super().process(resources)
92
93 def disable_protection(self, resources):
94 deletion_protected = [
95 r for r in resources if r['settings'].get('deletionProtectionEnabled')]
96 disable_protection = SqlInstanceEnableDeletion({}, self.manager)
97 disable_protection.process(deletion_protected)
98
99
100@SqlInstance.action_registry.register('stop')
101class SqlInstanceStop(MethodAction):
102
103 schema = type_schema('stop')
104 method_spec = {'op': 'patch'}
105 path_param_re = re.compile('.*?/projects/(.*?)/instances/(.*)')
106 method_perm = 'update'
107
108 def get_resource_params(self, model, resource):
109 project, instance = self.path_param_re.match(
110 resource['selfLink']).groups()
111 return {'project': project,
112 'instance': instance,
113 'body': {'settings': {'activationPolicy': 'NEVER'}}}
114
115
116@SqlInstance.action_registry.register('start')
117class SqlInstanceStart(MethodAction):
118
119 schema = type_schema('start')
120 method_spec = {'op': 'patch'}
121 path_param_re = re.compile('.*?/projects/(.*?)/instances/(.*)')
122 method_perm = 'update'
123
124 def get_resource_params(self, model, resource):
125 project, instance = self.path_param_re.match(
126 resource['selfLink']).groups()
127 return {'project': project,
128 'instance': instance,
129 'body': {'settings': {'activationPolicy': 'ALWAYS'}}}
130
131
132@SqlInstance.action_registry.register('set-deletion-protection')
133class SqlInstanceEnableDeletion(MethodAction):
134
135 schema = type_schema(
136 'set-deletion-protection',
137 value={'type': 'boolean'})
138 method_spec = {'op': 'patch'}
139 path_param_re = re.compile('.*?/projects/(.*?)/instances/(.*)')
140 method_perm = 'update'
141
142 def get_resource_params(self, model, resource):
143 project, instance = self.path_param_re.match(
144 resource['selfLink']).groups()
145 return {
146 'project': project,
147 'instance': instance,
148 'body': {
149 'settings': {
150 'deletionProtectionEnabled': str(self.data.get('value', True)).lower()
151 }
152 }
153 }
154
155
156@SqlInstance.action_registry.register('set-high-availability')
157class SqlInstanceHighAvailability(MethodAction):
158
159 schema = type_schema(
160 'set-high-availability',
161 value={'type': 'boolean', 'required': True})
162 method_spec = {'op': 'patch'}
163 path_param_re = re.compile('.*?/projects/(.*?)/instances/(.*)')
164 method_perm = 'update'
165
166 def get_resource_params(self, model, resource):
167 if self.data['value'] is False:
168 availabilityType = 'ZONAL'
169 else:
170 availabilityType = 'REGIONAL'
171
172 project, instance = self.path_param_re.match(
173 resource['selfLink']).groups()
174 return {
175 'project': project,
176 'instance': instance,
177 'body': {
178 'settings': {
179 'availabilityType': availabilityType
180 }
181 }
182 }
183
184
185class SQLInstanceChildTypeInfo(ChildTypeInfo):
186 service = 'sqladmin'
187 version = 'v1beta4'
188 parent_spec = {
189 'resource': 'sql-instance',
190 'child_enum_params': [
191 ('name', 'instance')
192 ]
193 }
194 perm_service = 'cloudsql'
195
196 @classmethod
197 def _get_location(cls, resource):
198 return super()._get_location(cls.get_parent(resource))
199
200 @classmethod
201 def _get_urn_id(cls, resource):
202 return f"{resource['instance']}/{resource[cls.id]}"
203
204
205@resources.register('sql-user')
206class SqlUser(ChildResourceManager):
207
208 class resource_type(SQLInstanceChildTypeInfo):
209 component = 'users'
210 enum_spec = ('list', 'items[]', None)
211 name = id = 'name'
212 default_report_fields = ["name", "project", "instance"]
213 urn_component = "user"
214
215
216class SqlInstanceChildWithSelfLink(ChildResourceManager):
217 """A ChildResourceManager for resources that reference SqlInstance in selfLink.
218 """
219
220 def _get_parent_resource_info(self, child_instance):
221 """
222 :param child_instance: a dictionary to get parent parameters from
223 :return: project_id and database_id extracted from child_instance
224 """
225 return {'project_id': re.match('.*?/projects/(.*?)/instances/.*',
226 child_instance['selfLink']).group(1),
227 'database_id': child_instance['instance']}
228
229
230@resources.register('sql-backup-run')
231class SqlBackupRun(SqlInstanceChildWithSelfLink):
232 """GCP Resource
233 https://cloud.google.com/sql/docs/mysql/admin-api/rest/v1beta4/backupRuns
234 """
235 class resource_type(SQLInstanceChildTypeInfo):
236 component = 'backupRuns'
237 enum_spec = ('list', 'items[]', None)
238 get_requires_event = True
239 name = id = 'id'
240 default_report_fields = [
241 name, "status", "instance", "location", "enqueuedTime", "startTime", "endTime"]
242 urn_component = "backup-run"
243
244 @staticmethod
245 def get(client, event):
246 project = jmespath_search('protoPayload.response.targetProject', event)
247 instance = jmespath_search('protoPayload.response.targetId', event)
248 insert_time = jmespath_search('protoPayload.response.insertTime', event)
249 parameters = {'project': project,
250 'instance': instance,
251 'id': SqlBackupRun.resource_type._from_insert_time_to_id(insert_time)}
252 return client.execute_command('get', parameters)
253
254 @staticmethod
255 def _from_insert_time_to_id(insert_time):
256 """
257 Backup Run id is not available in a log record directly.
258 Fortunately, as it is an integer timestamp representation,
259 it can be retrieved by converting raw insert_time value.
260
261 :param insert_time: a UTC ISO formatted date time string
262 :return: an integer number of microseconds since unix epoch
263 """
264 delta = parse(insert_time).replace(tzinfo=None) - datetime.utcfromtimestamp(0)
265 return int(delta.total_seconds()) * 1000 + int(delta.microseconds / 1000)
266
267
268@resources.register('sql-ssl-cert')
269class SqlSslCert(SqlInstanceChildWithSelfLink):
270 """GCP Resource
271 https://cloud.google.com/sql/docs/mysql/admin-api/rest/v1beta4/sslCerts
272 """
273 class resource_type(SQLInstanceChildTypeInfo):
274 component = 'sslCerts'
275 enum_spec = ('list', 'items[]', None)
276 get_requires_event = True
277 id = 'sha1Fingerprint'
278 name = "commonName"
279 default_report_fields = [
280 id, name, "instance", "expirationTime"]
281 urn_component = "ssl-cert"
282
283 @staticmethod
284 def get(client, event):
285 self_link = jmespath_search('protoPayload.response.clientCert.certInfo.selfLink', event)
286 self_link_re = '.*?/projects/(.*?)/instances/(.*?)/sslCerts/(.*)'
287 project, instance, sha_1_fingerprint = re.match(self_link_re, self_link).groups()
288 parameters = {'project': project,
289 'instance': instance,
290 'sha1Fingerprint': sha_1_fingerprint}
291 return client.execute_command('get', parameters)