Repository: cloud-custodian/cloud-custodian

Issue:
rds maintenance window enforcement
ie. ensure auto patching minor version is enabled on rds.



Discussion:

Resolving patch:
diff --git a/c7n/resources/rds.py b/c7n/resources/rds.py
--- a/c7n/resources/rds.py
+++ b/c7n/resources/rds.py
@@ -173,6 +173,27 @@ def process_resource_set(self, resources, tags):
             client.add_tags_to_resource(ResourceName=arn, Tags=tags)
 
 
+@actions.register('auto-patch')
+class AutoPatch(BaseAction):
+
+    schema = type_schema(
+        'auto-patch',
+        minor={'type': 'boolean'}, window={'type': 'string'})
+
+    def process(self, resources):
+        client = local_session(
+            self.manager.session_factory).client('rds')
+
+        params = {'AutoMinorVersionUpgrade': self.data.get('minor', True)}
+        if self.data.get('window'):
+            params['PreferredMaintenanceWindow'] = self.data['minor']
+
+        for r in resources:
+            client.modify_db_instance(
+                DBInstanceIdentifier=r['DBInstanceIdentifier'],
+                **params)
+
+
 @actions.register('tag')
 class Tag(tags.Tag):
 


Test patch:
diff --git a/tests/data/placebo/test_rds_auto_patch/iam.ListRoles_1.json b/tests/data/placebo/test_rds_auto_patch/iam.ListRoles_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_rds_auto_patch/iam.ListRoles_1.json
@@ -0,0 +1,35 @@
+{
+    "status_code": 200, 
+    "data": {
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200, 
+            "RequestId": "01041d4e-3bcd-11e6-ba67-69f7766ec929", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "01041d4e-3bcd-11e6-ba67-69f7766ec929", 
+                "date": "Sun, 26 Jun 2016 18:37:14 GMT", 
+                "content-length": "849", 
+                "content-type": "text/xml"
+            }
+        }, 
+        "IsTruncated": false, 
+        "Roles": [
+            {
+                "AssumeRolePolicyDocument": "%7B%22Version%22%3A%222012-10-17%22%2C%22Statement%22%3A%5B%7B%22Effect%22%3A%22Allow%22%2C%22Principal%22%3A%7B%22Service%22%3A%22lambda.amazonaws.com%22%7D%2C%22Action%22%3A%22sts%3AAssumeRole%22%7D%5D%7D", 
+                "RoleId": "AROAIDZBE5CMRCNENDQD2", 
+                "CreateDate": {
+                    "hour": 12, 
+                    "__class__": "datetime", 
+                    "month": 6, 
+                    "second": 5, 
+                    "microsecond": 0, 
+                    "year": 2016, 
+                    "day": 26, 
+                    "minute": 11
+                }, 
+                "RoleName": "lambda_basic_execution", 
+                "Path": "/", 
+                "Arn": "arn:aws:iam::644160558196:role/lambda_basic_execution"
+            }
+        ]
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_rds_auto_patch/rds.DescribeDBInstances_1.json b/tests/data/placebo/test_rds_auto_patch/rds.DescribeDBInstances_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_rds_auto_patch/rds.DescribeDBInstances_1.json
@@ -0,0 +1,116 @@
+{
+    "status_code": 200, 
+    "data": {
+        "DBInstances": [
+            {
+                "PubliclyAccessible": true, 
+                "MasterUsername": "mydbuser", 
+                "MonitoringInterval": 0, 
+                "LicenseModel": "postgresql-license", 
+                "VpcSecurityGroups": [
+                    {
+                        "Status": "active", 
+                        "VpcSecurityGroupId": "sg-f6a42890"
+                    }
+                ], 
+                "InstanceCreateTime": {
+                    "hour": 18, 
+                    "__class__": "datetime", 
+                    "month": 6, 
+                    "second": 54, 
+                    "microsecond": 265000, 
+                    "year": 2016, 
+                    "day": 26, 
+                    "minute": 23
+                }, 
+                "CopyTagsToSnapshot": false, 
+                "OptionGroupMemberships": [
+                    {
+                        "Status": "in-sync", 
+                        "OptionGroupName": "default:postgres-9-5"
+                    }
+                ], 
+                "PendingModifiedValues": {}, 
+                "Engine": "postgres", 
+                "MultiAZ": false, 
+                "LatestRestorableTime": {
+                    "hour": 18, 
+                    "__class__": "datetime", 
+                    "month": 6, 
+                    "second": 16, 
+                    "microsecond": 0, 
+                    "year": 2016, 
+                    "day": 26, 
+                    "minute": 30
+                }, 
+                "DBSecurityGroups": [], 
+                "DBParameterGroups": [
+                    {
+                        "DBParameterGroupName": "default.postgres9.5", 
+                        "ParameterApplyStatus": "in-sync"
+                    }
+                ], 
+                "AutoMinorVersionUpgrade": false, 
+                "PreferredBackupWindow": "11:03-11:33", 
+                "DBSubnetGroup": {
+                    "Subnets": [
+                        {
+                            "SubnetStatus": "Active", 
+                            "SubnetIdentifier": "subnet-15452171", 
+                            "SubnetAvailabilityZone": {
+                                "Name": "us-west-2a"
+                            }
+                        }, 
+                        {
+                            "SubnetStatus": "Active", 
+                            "SubnetIdentifier": "subnet-63c97615", 
+                            "SubnetAvailabilityZone": {
+                                "Name": "us-west-2b"
+                            }
+                        }, 
+                        {
+                            "SubnetStatus": "Active", 
+                            "SubnetIdentifier": "subnet-5fb47507", 
+                            "SubnetAvailabilityZone": {
+                                "Name": "us-west-2c"
+                            }
+                        }
+                    ], 
+                    "DBSubnetGroupName": "default", 
+                    "VpcId": "vpc-4a9ff72e", 
+                    "DBSubnetGroupDescription": "default", 
+                    "SubnetGroupStatus": "Complete"
+                }, 
+                "ReadReplicaDBInstanceIdentifiers": [], 
+                "AllocatedStorage": 5, 
+                "BackupRetentionPeriod": 7, 
+                "PreferredMaintenanceWindow": "tue:07:37-tue:08:07", 
+                "Endpoint": {
+                    "Port": 5432, 
+                    "Address": "mydb.cnhganveedh2.us-west-2.rds.amazonaws.com"
+                }, 
+                "DBInstanceStatus": "available", 
+                "EngineVersion": "9.5.2", 
+                "AvailabilityZone": "us-west-2b", 
+                "DomainMemberships": [], 
+                "StorageType": "gp2", 
+                "DbiResourceId": "db-OCZWPGLNE2BOYMEW72NS2SBJCU", 
+                "CACertificateIdentifier": "rds-ca-2015", 
+                "StorageEncrypted": false, 
+                "DBInstanceClass": "db.m3.medium", 
+                "DbInstancePort": 0, 
+                "DBInstanceIdentifier": "mydb"
+            }
+        ], 
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200, 
+            "RequestId": "00c05d6f-3bcd-11e6-a2d2-83999282af9f", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "00c05d6f-3bcd-11e6-a2d2-83999282af9f", 
+                "date": "Sun, 26 Jun 2016 18:37:14 GMT", 
+                "content-length": "3862", 
+                "content-type": "text/xml"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_rds_auto_patch/rds.ListTagsForResource_1.json b/tests/data/placebo/test_rds_auto_patch/rds.ListTagsForResource_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_rds_auto_patch/rds.ListTagsForResource_1.json
@@ -0,0 +1,21 @@
+{
+    "status_code": 200, 
+    "data": {
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200, 
+            "RequestId": "01c634e2-3bcd-11e6-b40d-4d5abe3713c4", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "01c634e2-3bcd-11e6-b40d-4d5abe3713c4", 
+                "date": "Sun, 26 Jun 2016 18:37:15 GMT", 
+                "content-length": "394", 
+                "content-type": "text/xml"
+            }
+        }, 
+        "TagList": [
+            {
+                "Value": "other", 
+                "Key": "workload-type"
+            }
+        ]
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_rds_auto_patch/rds.ModifyDBInstance_1.json b/tests/data/placebo/test_rds_auto_patch/rds.ModifyDBInstance_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_rds_auto_patch/rds.ModifyDBInstance_1.json
@@ -0,0 +1,114 @@
+{
+    "status_code": 200, 
+    "data": {
+        "DBInstance": {
+            "PubliclyAccessible": true, 
+            "MasterUsername": "mydbuser", 
+            "MonitoringInterval": 0, 
+            "LicenseModel": "postgresql-license", 
+            "VpcSecurityGroups": [
+                {
+                    "Status": "active", 
+                    "VpcSecurityGroupId": "sg-f6a42890"
+                }
+            ], 
+            "InstanceCreateTime": {
+                "hour": 18, 
+                "__class__": "datetime", 
+                "month": 6, 
+                "second": 54, 
+                "microsecond": 265000, 
+                "year": 2016, 
+                "day": 26, 
+                "minute": 23
+            }, 
+            "CopyTagsToSnapshot": false, 
+            "OptionGroupMemberships": [
+                {
+                    "Status": "in-sync", 
+                    "OptionGroupName": "default:postgres-9-5"
+                }
+            ], 
+            "PendingModifiedValues": {}, 
+            "Engine": "postgres", 
+            "MultiAZ": false, 
+            "LatestRestorableTime": {
+                "hour": 18, 
+                "__class__": "datetime", 
+                "month": 6, 
+                "second": 16, 
+                "microsecond": 0, 
+                "year": 2016, 
+                "day": 26, 
+                "minute": 30
+            }, 
+            "DBSecurityGroups": [], 
+            "DBParameterGroups": [
+                {
+                    "DBParameterGroupName": "default.postgres9.5", 
+                    "ParameterApplyStatus": "in-sync"
+                }
+            ], 
+            "AutoMinorVersionUpgrade": true, 
+            "PreferredBackupWindow": "11:03-11:33", 
+            "DBSubnetGroup": {
+                "Subnets": [
+                    {
+                        "SubnetStatus": "Active", 
+                        "SubnetIdentifier": "subnet-15452171", 
+                        "SubnetAvailabilityZone": {
+                            "Name": "us-west-2a"
+                        }
+                    }, 
+                    {
+                        "SubnetStatus": "Active", 
+                        "SubnetIdentifier": "subnet-63c97615", 
+                        "SubnetAvailabilityZone": {
+                            "Name": "us-west-2b"
+                        }
+                    }, 
+                    {
+                        "SubnetStatus": "Active", 
+                        "SubnetIdentifier": "subnet-5fb47507", 
+                        "SubnetAvailabilityZone": {
+                            "Name": "us-west-2c"
+                        }
+                    }
+                ], 
+                "DBSubnetGroupName": "default", 
+                "VpcId": "vpc-4a9ff72e", 
+                "DBSubnetGroupDescription": "default", 
+                "SubnetGroupStatus": "Complete"
+            }, 
+            "ReadReplicaDBInstanceIdentifiers": [], 
+            "AllocatedStorage": 5, 
+            "BackupRetentionPeriod": 7, 
+            "PreferredMaintenanceWindow": "tue:07:37-tue:08:07", 
+            "Endpoint": {
+                "Port": 5432, 
+                "Address": "mydb.cnhganveedh2.us-west-2.rds.amazonaws.com"
+            }, 
+            "DBInstanceStatus": "available", 
+            "EngineVersion": "9.5.2", 
+            "AvailabilityZone": "us-west-2b", 
+            "DomainMemberships": [], 
+            "StorageType": "gp2", 
+            "DbiResourceId": "db-OCZWPGLNE2BOYMEW72NS2SBJCU", 
+            "CACertificateIdentifier": "rds-ca-2015", 
+            "StorageEncrypted": false, 
+            "DBInstanceClass": "db.m3.medium", 
+            "DbInstancePort": 0, 
+            "DBInstanceIdentifier": "mydb"
+        }, 
+        "ResponseMetadata": {
+            "HTTPStatusCode": 200, 
+            "RequestId": "02108492-3bcd-11e6-bf1d-0ba3404c2839", 
+            "HTTPHeaders": {
+                "x-amzn-requestid": "02108492-3bcd-11e6-bf1d-0ba3404c2839", 
+                "date": "Sun, 26 Jun 2016 18:37:16 GMT", 
+                "content-length": "3650", 
+                "content-type": "text/xml"
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/test_rds.py b/tests/test_rds.py
--- a/tests/test_rds.py
+++ b/tests/test_rds.py
@@ -19,6 +19,18 @@
 
 class RDSTest(BaseTest):
 
+    def test_rds_autopatch(self):
+        session_factory = self.replay_flight_data('test_rds_auto_patch')
+        p = self.load_policy({
+            'name': 'rds-tags',
+            'resource': 'rds',
+            'filters': [
+                {'AutoMinorVersionUpgrade': False}],
+            'actions': ['auto-patch']},
+            session_factory=session_factory)
+        resources = p.run()
+        self.assertEqual(len(resources), 1)
+
     def test_rds_tags(self):
         session_factory = self.replay_flight_data('test_rds_tags')
         p = self.load_policy({