1# Copyright The Cloud Custodian Authors.
2# SPDX-License-Identifier: Apache-2.0
3import logging
4import itertools
5
6from botocore.exceptions import ClientError
7
8from c7n.actions import ActionRegistry, BaseAction
9from c7n.filters import FilterRegistry, ValueFilter
10from c7n.manager import resources
11from c7n.query import QueryResourceManager, TypeInfo
12from c7n.utils import (type_schema, local_session, chunks)
13from c7n.tags import universal_augment
14from c7n.resources.rds import ParameterFilter
15
16log = logging.getLogger('custodian.rds-param-group')
17
18pg_filters = FilterRegistry('rds-param-group.filters')
19pg_actions = ActionRegistry('rds-param-group.actions')
20
21
22@resources.register('rds-param-group')
23class RDSParamGroup(QueryResourceManager):
24 """Resource manager for RDS parameter groups.
25 """
26
27 class resource_type(TypeInfo):
28
29 service = 'rds'
30 arn_type = 'pg'
31 enum_spec = ('describe_db_parameter_groups', 'DBParameterGroups', None)
32 name = id = 'DBParameterGroupName'
33 arn = 'DBParameterGroupArn'
34 dimension = 'DBParameterGroupName'
35 permissions_enum = ('rds:DescribeDBParameterGroups',)
36 cfn_type = 'AWS::RDS::DBParameterGroup'
37 universal_taggable = object()
38 permissions_augment = ("rds:ListTagsForResource",)
39
40 augment = universal_augment
41
42 filter_registry = pg_filters
43 action_registry = pg_actions
44
45
46pg_cluster_filters = FilterRegistry('rds-cluster-param-group.filters')
47pg_cluster_actions = ActionRegistry('rds-cluster-param-group.actions')
48
49
50@resources.register('rds-cluster-param-group')
51class RDSClusterParamGroup(QueryResourceManager):
52 """ Resource manager for RDS cluster parameter groups.
53 """
54
55 class resource_type(TypeInfo):
56
57 service = 'rds'
58 arn_type = 'cluster-pg'
59 arn = 'DBClusterParameterGroupArn'
60 enum_spec = ('describe_db_cluster_parameter_groups', 'DBClusterParameterGroups', None)
61 name = id = 'DBClusterParameterGroupName'
62 dimension = 'DBClusterParameterGroupName'
63 permissions_enum = ('rds:DescribeDBClusterParameterGroups',)
64 cfn_type = 'AWS::RDS::DBClusterParameterGroup'
65 universal_taggable = object()
66
67 augment = universal_augment
68
69 filter_registry = pg_cluster_filters
70 action_registry = pg_cluster_actions
71
72
73class PGMixin:
74
75 def get_pg_name(self, pg):
76 return pg['DBParameterGroupName']
77
78
79class PGClusterMixin:
80
81 def get_pg_name(self, pg):
82 return pg['DBClusterParameterGroupName']
83
84
85class ParameterGroupFilter(ParameterFilter):
86 """
87 Applies value type filter on db parameter values.
88
89 """
90
91 schema = type_schema('db-parameter', rinherit=ValueFilter.schema)
92 schema_alias = False
93 permissions = ('rds:DescribeDBParameters',)
94 policy_annotation = 'c7n:MatchedDBParameter'
95
96 def get_pg_values(self, param_group):
97 pgvalues = {}
98 param_list = self.get_param_list(param_group)
99 pgvalues = {
100 p['ParameterName']: ParameterFilter.recast(p['ParameterValue'], p['DataType'])
101 for p in param_list if 'ParameterValue' in p}
102 return pgvalues
103
104 def process(self, resources, event=None):
105 results = []
106 for resource in resources:
107 name = self.get_pg_name(resource)
108 pg_values = self.get_pg_values(name)
109
110 if self.match(pg_values):
111 resource.setdefault(self.policy_annotation, []).append(
112 self.data.get('key'))
113 results.append(resource)
114
115 return results
116
117
118@pg_filters.register('db-parameter')
119class PGParameterFilter(PGMixin, ParameterGroupFilter):
120 """ Filter by parameters.
121
122 :example:
123
124 .. code-block:: yaml
125
126 policies:
127 - name: rds-param-group-param-filter
128 resource: rds-param-group
129 filters:
130 - type: db-parameter
131 key: someparam
132 op: eq
133 value: someval
134 """
135
136 def get_param_list(self, pg):
137 client = local_session(self.manager.session_factory).client('rds')
138 paginator = client.get_paginator('describe_db_parameters')
139 param_list = list(itertools.chain(*[p['Parameters']
140 for p in paginator.paginate(DBParameterGroupName=pg)]))
141
142 return param_list
143
144
145@pg_cluster_filters.register('db-parameter')
146class PGClusterParameterFilter(PGClusterMixin, ParameterGroupFilter):
147 """ Filter by parameters.
148
149 :example:
150
151 .. code-block:: yaml
152
153 policies:
154 - name: rds-cluster-param-group-param-filter
155 resource: rds-cluster-param-group
156 filters:
157 - type: db-parameter
158 key: someparam
159 op: eq
160 value: someval
161 """
162
163 def get_param_list(self, pg):
164 client = local_session(self.manager.session_factory).client('rds')
165 paginator = client.get_paginator('describe_db_cluster_parameters')
166 param_list = list(itertools.chain(*[p['Parameters']
167 for p in paginator.paginate(DBClusterParameterGroupName=pg)]))
168
169 return param_list
170
171
172class Copy(BaseAction):
173
174 schema = type_schema(
175 'copy',
176 **{
177 'required': ['name'],
178 'name': {'type': 'string'},
179 'description': {'type': 'string'},
180 }
181 )
182
183 def process(self, param_groups):
184 client = local_session(self.manager.session_factory).client('rds')
185
186 for param_group in param_groups:
187 name = self.get_pg_name(param_group)
188 copy_name = self.data.get('name')
189 copy_desc = self.data.get('description', 'Copy of {}'.format(name))
190 self.do_copy(client, name, copy_name, copy_desc)
191 self.log.info('Copied RDS parameter group %s to %s', name, copy_name)
192
193
194@pg_actions.register('copy')
195class PGCopy(PGMixin, Copy):
196 """ Action to copy an RDS parameter group.
197
198 :example:
199
200 .. code-block:: yaml
201
202 policies:
203 - name: rds-param-group-copy
204 resource: rds-param-group
205 filters:
206 - DBParameterGroupName: original_pg_name
207 actions:
208 - type: copy
209 name: copy_name
210 """
211
212 permissions = ('rds:CopyDBParameterGroup',)
213
214 def do_copy(self, client, name, copy_name, desc):
215 client.copy_db_parameter_group(
216 SourceDBParameterGroupIdentifier=name,
217 TargetDBParameterGroupIdentifier=copy_name,
218 TargetDBParameterGroupDescription=desc
219 )
220
221
222@pg_cluster_actions.register('copy')
223class PGClusterCopy(PGClusterMixin, Copy):
224 """ Action to copy an RDS cluster parameter group.
225
226 :example:
227
228 .. code-block:: yaml
229
230 policies:
231 - name: rds-cluster-param-group-copy
232 resource: rds-cluster-param-group
233 filters:
234 - DBClusterParameterGroupName: original_cluster_pg_name
235 actions:
236 - type: copy
237 name: copy_name
238 """
239
240 permissions = ('rds:CopyDBClusterParameterGroup',)
241
242 def do_copy(self, client, name, copy_name, desc):
243 client.copy_db_cluster_parameter_group(
244 SourceDBClusterParameterGroupIdentifier=name,
245 TargetDBClusterParameterGroupIdentifier=copy_name,
246 TargetDBClusterParameterGroupDescription=desc
247 )
248
249
250class Delete(BaseAction):
251
252 schema = type_schema('delete')
253
254 def process(self, param_groups):
255 client = local_session(self.manager.session_factory).client('rds')
256
257 for param_group in param_groups:
258 name = self.get_pg_name(param_group)
259 try:
260 self.do_delete(client, name)
261 except ClientError as e:
262 if e.response['Error']['Code'] == 'DBParameterGroupNotFoundFault':
263 self.log.warning('RDS parameter group %s already deleted', name)
264 continue
265 raise
266 self.log.info('Deleted RDS parameter group: %s', name)
267
268
269@pg_actions.register('delete')
270class PGDelete(PGMixin, Delete):
271 """Action to delete an RDS parameter group
272
273 :example:
274
275 .. code-block:: yaml
276
277 policies:
278 - name: rds-param-group-delete
279 resource: rds-param-group
280 filters:
281 - DBParameterGroupName: pg_name
282 actions:
283 - type: delete
284 """
285
286 permissions = ('rds:DeleteDBParameterGroup',)
287
288 def do_delete(self, client, name):
289 client.delete_db_parameter_group(DBParameterGroupName=name)
290
291
292@pg_cluster_actions.register('delete')
293class PGClusterDelete(PGClusterMixin, Delete):
294 """Action to delete an RDS cluster parameter group
295
296 :example:
297
298 .. code-block:: yaml
299
300 policies:
301 - name: rds-cluster-param-group-delete
302 resource: rds-cluster-param-group
303 filters:
304 - DBClusterParameterGroupName: cluster_pg_name
305 actions:
306 - type: delete
307 """
308
309 permissions = ('rds:DeleteDBClusterParameterGroup',)
310
311 def do_delete(self, client, name):
312 client.delete_db_cluster_parameter_group(DBClusterParameterGroupName=name)
313
314
315class Modify(BaseAction):
316
317 schema = type_schema(
318 'modify',
319 **{
320 'required': ['params'],
321 'params': {
322 'type': 'array',
323 'items': {
324 'type': 'object',
325 'required': ['name', 'value'],
326 'name': {'type': 'string'},
327 'value': {'type': 'string'},
328 'apply-method': {'type': 'string', 'enum': ['immediate', 'pending-reboot']}
329 },
330 },
331 }
332 )
333
334 def process(self, param_groups):
335 client = local_session(self.manager.session_factory).client('rds')
336
337 params = []
338 for param in self.data.get('params', []):
339 params.append({
340 'ParameterName': param['name'],
341 'ParameterValue': param['value'],
342 'ApplyMethod': param.get('apply-method', 'immediate'),
343 })
344
345 for param_group in param_groups:
346 name = self.get_pg_name(param_group)
347
348 # Fetch the existing parameters for this DB, so we only try to change the ones that are
349 # different.
350 cur_params = self.get_current_params(client, name)
351 changed_params = []
352 for param in params:
353 param_name = param['ParameterName']
354 if (param_name not in cur_params or
355 cur_params[param_name]['ParameterValue'] != param['ParameterValue']):
356 changed_params.append(param)
357
358 # Can only do 20 elements at a time per docs, so if we have more than that we will
359 # break it into multiple requests:
360 # https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/rds.html#RDS.Client.modify_db_parameter_group
361 for param_set in chunks(changed_params, 5):
362 self.do_modify(client, name, param_set)
363
364 self.log.info('Modified RDS parameter group %s (%i parameters changed, %i unchanged)',
365 name, len(changed_params), len(params) - len(changed_params))
366
367
368@pg_actions.register('modify')
369class PGModify(PGMixin, Modify):
370 """Action to modify an RDS parameter group
371
372 :example:
373
374 .. code-block:: yaml
375
376 policies:
377 - name: rds-param-group-modify
378 resource: rds-param-group
379 filters:
380 - DBParameterGroupName: pg_name
381 actions:
382 - type: modify
383 params:
384 - name: autocommit
385 value: "1"
386 - name: max_connections
387 value: "100"
388 """
389
390 permissions = ('rds:DescribeDBParameters', 'rds:ModifyDBParameterGroup')
391
392 def get_current_params(self, client, name):
393 params = client.describe_db_parameters(DBParameterGroupName=name)
394 return {x['ParameterName']: {
395 'ParameterValue': x.get('ParameterValue'),
396 'ApplyMethod': x['ApplyMethod']}
397 for x in params.get('Parameters', [])}
398
399 def do_modify(self, client, name, params):
400 client.modify_db_parameter_group(DBParameterGroupName=name, Parameters=params)
401
402
403@pg_cluster_actions.register('modify')
404class PGClusterModify(PGClusterMixin, Modify):
405 """Action to modify an RDS cluster parameter group
406
407 :example:
408
409 .. code-block:: yaml
410
411 policies:
412 - name: rds-cluster-param-group-modify
413 resource: rds-cluster-param-group
414 filters:
415 - DBClusterParameterGroupName: cluster_pg_name
416 actions:
417 - type: modify
418 params:
419 - name: lower_case_table_names
420 value: "1"
421 - name: master_verify_checksum
422 value: "1"
423 """
424
425 permissions = ('rds:DescribeDBClusterParameters', 'rds:ModifyDBClusterParameterGroup')
426
427 def get_current_params(self, client, name):
428 params = client.describe_db_cluster_parameters(DBClusterParameterGroupName=name)
429 return {x['ParameterName']: {
430 'ParameterValue': x.get('ParameterValue'),
431 'ApplyMethod': x['ApplyMethod']}
432 for x in params.get('Parameters', [])}
433
434 def do_modify(self, client, name, params):
435 client.modify_db_cluster_parameter_group(
436 DBClusterParameterGroupName=name,
437 Parameters=params
438 )