Repository: ansible/ansible

Issue:
rds.py reverts multi-AZ RDS back to single-AZ on any 'modify' action where 'multi_zone' is not specified
##### ISSUE TYPE
 - Bug Report

##### COMPONENT NAME
rds.py

##### ANSIBLE VERSION
```
ansible 2.3.0.0 (stable-2.3 0fe53eda86) last updated 2017/04/06 22:20:16 (GMT -500)
  config file = 
  configured module search path = Default w/o overrides
  python version = 2.7.13 (default, Dec 18 2016, 07:03:39) [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)]

```

##### CONFIGURATION
n/a

##### OS / ENVIRONMENT
n/a

##### SUMMARY
Any 'modify' action using the rds.py module taken against a multi-AZ RDS, where the optional `multi_zone` parameter is not explicitly included in the task, results in the RDS being modified to a single-AZ RDS.

##### STEPS TO REPRODUCE
Example playbook:

<!--- Paste example playbooks or commands between quotes below -->
```
---

- name: Update password on RDS
  hosts: localhost
  tasks:
    - name: Apply new password to RDS
      rds:
        region: us-east-1
        command: modify
        instance_name: some-rds-12345
        password: supersekrit123

```

##### EXPECTED RESULTS
The `password` and/or other specified parameters are updated, while the `multi_zone` (multi-AZ) status of the RDS is left alone unless specified.

##### ACTUAL RESULTS
The requested parameters are updated but in addition, the RDS is changed as if we had specified `multi_zone` to false (ie. multi-AZ is disabled).

rds.py reverts multi-AZ RDS back to single-AZ on any 'modify' action where 'multi_zone' is not specified
##### ISSUE TYPE
 - Bug Report

##### COMPONENT NAME
rds.py

##### ANSIBLE VERSION
```
ansible 2.3.0.0 (stable-2.3 0fe53eda86) last updated 2017/04/06 22:20:16 (GMT -500)
  config file = 
  configured module search path = Default w/o overrides
  python version = 2.7.13 (default, Dec 18 2016, 07:03:39) [GCC 4.2.1 Compatible Apple LLVM 8.0.0 (clang-800.0.42.1)]

```

##### CONFIGURATION
n/a

##### OS / ENVIRONMENT
n/a

##### SUMMARY
Any 'modify' action using the rds.py module taken against a multi-AZ RDS, where the optional `multi_zone` parameter is not explicitly included in the task, results in the RDS being modified to a single-AZ RDS.

##### STEPS TO REPRODUCE
Example playbook:

<!--- Paste example playbooks or commands between quotes below -->
```
---

- name: Update password on RDS
  hosts: localhost
  tasks:
    - name: Apply new password to RDS
      rds:
        region: us-east-1
        command: modify
        instance_name: some-rds-12345
        password: supersekrit123

```

##### EXPECTED RESULTS
The `password` and/or other specified parameters are updated, while the `multi_zone` (multi-AZ) status of the RDS is left alone unless specified.

##### ACTUAL RESULTS
The requested parameters are updated but in addition, the RDS is changed as if we had specified `multi_zone` to false (ie. multi-AZ is disabled).



Discussion:
cc @bpennypacker @willthames
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
Confirming this. [multi_zone defaults to False](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/amazon/rds.py#L1084) rather than None so this line https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/amazon/rds.py#L1040 does not work as expected when multi-az isn't specified.
cc @bpennypacker @willthames
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
Confirming this. [multi_zone defaults to False](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/amazon/rds.py#L1084) rather than None so this line https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/amazon/rds.py#L1040 does not work as expected when multi-az isn't specified.

Resolving patch:
diff --git a/lib/ansible/modules/cloud/amazon/rds.py b/lib/ansible/modules/cloud/amazon/rds.py
--- a/lib/ansible/modules/cloud/amazon/rds.py
+++ b/lib/ansible/modules/cloud/amazon/rds.py
@@ -340,7 +340,7 @@
 except ImportError:
     has_rds2 = False
 
-DEFAULT_PORTS= {
+DEFAULT_PORTS = {
     'aurora': 3306,
     'mariadb': 3306,
     'mysql': 3306,
@@ -349,6 +349,7 @@
     'postgres': 5432,
 }
 
+
 class RDSException(Exception):
     def __init__(self, exc):
         if hasattr(exc, 'error_message') and exc.error_message:
@@ -365,7 +366,7 @@ def __init__(self, exc):
 class RDSConnection:
     def __init__(self, module, region, **aws_connect_params):
         try:
-            self.connection  = connect_to_aws(boto.rds, region, **aws_connect_params)
+            self.connection = connect_to_aws(boto.rds, region, **aws_connect_params)
         except boto.exception.BotoServerError as e:
             module.fail_json(msg=e.error_message)
 
@@ -382,11 +383,11 @@ def get_db_snapshot(self, snapshotid):
             return None
 
     def create_db_instance(self, instance_name, size, instance_class, db_engine,
-            username, password, **params):
+                           username, password, **params):
         params['engine'] = db_engine
         try:
             result = self.connection.create_dbinstance(instance_name, size, instance_class,
-                    username, password, **params)
+                                                       username, password, **params)
             return RDSDBInstance(result)
         except boto.exception.BotoServerError as e:
             raise RDSException(e)
@@ -451,7 +452,7 @@ def promote_read_replica(self, instance_name, **params):
 class RDS2Connection:
     def __init__(self, module, region, **aws_connect_params):
         try:
-            self.connection  = connect_to_aws(boto.rds2, region, **aws_connect_params)
+            self.connection = connect_to_aws(boto.rds2, region, **aws_connect_params)
         except boto.exception.BotoServerError as e:
             module.fail_json(msg=e.error_message)
 
@@ -460,7 +461,7 @@ def get_db_instance(self, instancename):
             dbinstances = self.connection.describe_db_instances(
                 db_instance_identifier=instancename
             )['DescribeDBInstancesResponse']['DescribeDBInstancesResult']['DBInstances']
-            result =  RDS2DBInstance(dbinstances[0])
+            result = RDS2DBInstance(dbinstances[0])
             return result
         except boto.rds2.exceptions.DBInstanceNotFound as e:
             return None
@@ -479,10 +480,10 @@ def get_db_snapshot(self, snapshotid):
             return None
 
     def create_db_instance(self, instance_name, size, instance_class, db_engine,
-            username, password, **params):
+                           username, password, **params):
         try:
-            result = self.connection.create_db_instance(instance_name, size, instance_class,
-                db_engine, username, password, **params)['CreateDBInstanceResponse']['CreateDBInstanceResult']['DBInstance']
+            result = self.connection.create_db_instance(instance_name, size, instance_class, db_engine, username, password,
+                                                        **params)['CreateDBInstanceResponse']['CreateDBInstanceResult']['DBInstance']
             return RDS2DBInstance(result)
         except boto.exception.BotoServerError as e:
             raise RDSException(e)
@@ -560,18 +561,18 @@ def __init__(self, dbinstance):
 
     def get_data(self):
         d = {
-            'id'                 : self.name,
-            'create_time'        : self.instance.create_time,
-            'status'             : self.status,
-            'availability_zone'  : self.instance.availability_zone,
-            'backup_retention'   : self.instance.backup_retention_period,
-            'backup_window'      : self.instance.preferred_backup_window,
-            'maintenance_window' : self.instance.preferred_maintenance_window,
-            'multi_zone'         : self.instance.multi_az,
-            'instance_type'      : self.instance.instance_class,
-            'username'           : self.instance.master_username,
-            'iops'               : self.instance.iops
-            }
+            'id': self.name,
+            'create_time': self.instance.create_time,
+            'status': self.status,
+            'availability_zone': self.instance.availability_zone,
+            'backup_retention': self.instance.backup_retention_period,
+            'backup_window': self.instance.preferred_backup_window,
+            'maintenance_window': self.instance.preferred_maintenance_window,
+            'multi_zone': self.instance.multi_az,
+            'instance_type': self.instance.instance_class,
+            'username': self.instance.master_username,
+            'iops': self.instance.iops
+        }
 
         # Only assign an Endpoint if one is available
         if hasattr(self.instance, 'endpoint'):
@@ -594,8 +595,6 @@ def get_data(self):
         return d
 
 
-
-
 class RDS2DBInstance:
     def __init__(self, dbinstance):
         self.instance = dbinstance
@@ -639,12 +638,12 @@ def __init__(self, snapshot):
 
     def get_data(self):
         d = {
-            'id'                 : self.name,
-            'create_time'        : self.snapshot.snapshot_create_time,
-            'status'             : self.status,
-            'availability_zone'  : self.snapshot.availability_zone,
-            'instance_id'        : self.snapshot.instance_id,
-            'instance_created'   : self.snapshot.instance_create_time,
+            'id': self.name,
+            'create_time': self.snapshot.snapshot_create_time,
+            'status': self.status,
+            'availability_zone': self.snapshot.availability_zone,
+            'instance_id': self.snapshot.instance_id,
+            'instance_created': self.snapshot.instance_create_time,
         }
         # needs boto >= 2.21.0
         if hasattr(self.snapshot, 'snapshot_type'):
@@ -665,14 +664,14 @@ def __init__(self, snapshot):
 
     def get_data(self):
         d = {
-            'id'                 : self.name,
-            'create_time'        : self.snapshot['SnapshotCreateTime'],
-            'status'             : self.status,
-            'availability_zone'  : self.snapshot['AvailabilityZone'],
-            'instance_id'        : self.snapshot['DBInstanceIdentifier'],
-            'instance_created'   : self.snapshot['InstanceCreateTime'],
-            'snapshot_type'      : self.snapshot['SnapshotType'],
-            'iops'               : self.snapshot['Iops'],
+            'id': self.name,
+            'create_time': self.snapshot['SnapshotCreateTime'],
+            'status': self.status,
+            'availability_zone': self.snapshot['AvailabilityZone'],
+            'instance_id': self.snapshot['DBInstanceIdentifier'],
+            'instance_created': self.snapshot['InstanceCreateTime'],
+            'snapshot_type': self.snapshot['SnapshotType'],
+            'iops': self.snapshot['Iops'],
         }
         return d
 
@@ -712,7 +711,7 @@ def create_db_instance(module, conn):
     valid_vars = ['backup_retention', 'backup_window',
                   'character_set_name', 'db_name', 'engine_version',
                   'instance_type', 'iops', 'license_model', 'maint_window',
-                  'multi_zone', 'option_group', 'parameter_group','port',
+                  'multi_zone', 'option_group', 'parameter_group', 'port',
                   'subnet', 'upgrade', 'zone']
     if module.params.get('subnet'):
         valid_vars.append('vpc_security_groups')
@@ -729,8 +728,8 @@ def create_db_instance(module, conn):
     else:
         try:
             result = conn.create_db_instance(instance_name, module.params.get('size'),
-                    module.params.get('instance_type'), module.params.get('db_engine'),
-                module.params.get('username'), module.params.get('password'), **params)
+                                             module.params.get('instance_type'), module.params.get('db_engine'),
+                                             module.params.get('username'), module.params.get('password'), **params)
             changed = True
         except RDSException as e:
             module.fail_json(msg="Failed to create instance: %s" % e.message)
@@ -1068,40 +1067,40 @@ def validate_parameters(required_vars, valid_vars, module):
 def main():
     argument_spec = ec2_argument_spec()
     argument_spec.update(dict(
-        command           = dict(choices=['create', 'replicate', 'delete', 'facts', 'modify', 'promote', 'snapshot', 'reboot', 'restore'], required=True),
-        instance_name     = dict(required=False),
-        source_instance   = dict(required=False),
-        db_engine         = dict(choices=['mariadb', 'MySQL', 'oracle-se1', 'oracle-se', 'oracle-ee', 'sqlserver-ee', 'sqlserver-se', 'sqlserver-ex',
-                                          'sqlserver-web', 'postgres', 'aurora'], required=False),
-        size              = dict(required=False),
-        instance_type     = dict(aliases=['type'], required=False),
-        username          = dict(required=False),
-        password          = dict(no_log=True, required=False),
-        db_name           = dict(required=False),
-        engine_version    = dict(required=False),
-        parameter_group   = dict(required=False),
-        license_model     = dict(choices=['license-included', 'bring-your-own-license', 'general-public-license', 'postgresql-license'], required=False),
-        multi_zone        = dict(type='bool', default=False),
-        iops              = dict(required=False),
-        security_groups   = dict(required=False),
-        vpc_security_groups = dict(type='list', required=False),
-        port              = dict(required=False),
-        upgrade           = dict(type='bool', default=False),
-        option_group      = dict(required=False),
-        maint_window      = dict(required=False),
-        backup_window     = dict(required=False),
-        backup_retention  = dict(required=False),
-        zone              = dict(aliases=['aws_zone', 'ec2_zone'], required=False),
-        subnet            = dict(required=False),
-        wait              = dict(type='bool', default=False),
-        wait_timeout      = dict(type='int', default=300),
-        snapshot          = dict(required=False),
-        apply_immediately = dict(type='bool', default=False),
-        new_instance_name = dict(required=False),
-        tags              = dict(type='dict', required=False),
-        publicly_accessible = dict(required=False),
-        character_set_name = dict(required=False),
-        force_failover    = dict(type='bool', required=False, default=False)
+        command=dict(choices=['create', 'replicate', 'delete', 'facts', 'modify', 'promote', 'snapshot', 'reboot', 'restore'], required=True),
+        instance_name=dict(required=False),
+        source_instance=dict(required=False),
+        db_engine=dict(choices=['mariadb', 'MySQL', 'oracle-se1', 'oracle-se', 'oracle-ee', 'sqlserver-ee', 'sqlserver-se', 'sqlserver-ex',
+                                'sqlserver-web', 'postgres', 'aurora'], required=False),
+        size=dict(required=False),
+        instance_type=dict(aliases=['type'], required=False),
+        username=dict(required=False),
+        password=dict(no_log=True, required=False),
+        db_name=dict(required=False),
+        engine_version=dict(required=False),
+        parameter_group=dict(required=False),
+        license_model=dict(choices=['license-included', 'bring-your-own-license', 'general-public-license', 'postgresql-license'], required=False),
+        multi_zone=dict(type='bool', required=False),
+        iops=dict(required=False),
+        security_groups=dict(required=False),
+        vpc_security_groups=dict(type='list', required=False),
+        port=dict(required=False),
+        upgrade=dict(type='bool', default=False),
+        option_group=dict(required=False),
+        maint_window=dict(required=False),
+        backup_window=dict(required=False),
+        backup_retention=dict(required=False),
+        zone=dict(aliases=['aws_zone', 'ec2_zone'], required=False),
+        subnet=dict(required=False),
+        wait=dict(type='bool', default=False),
+        wait_timeout=dict(type='int', default=300),
+        snapshot=dict(required=False),
+        apply_immediately=dict(type='bool', default=False),
+        new_instance_name=dict(required=False),
+        tags=dict(type='dict', required=False),
+        publicly_accessible=dict(required=False),
+        character_set_name=dict(required=False),
+        force_failover=dict(type='bool', required=False, default=False)
     )
     )
 


Test patch:
diff --git a/test/sanity/pep8/legacy-files.txt b/test/sanity/pep8/legacy-files.txt
--- a/test/sanity/pep8/legacy-files.txt
+++ b/test/sanity/pep8/legacy-files.txt
@@ -204,7 +204,6 @@ lib/ansible/modules/cloud/amazon/iam_policy.py
 lib/ansible/modules/cloud/amazon/iam_server_certificate_facts.py
 lib/ansible/modules/cloud/amazon/lambda.py
 lib/ansible/modules/cloud/amazon/lambda_facts.py
-lib/ansible/modules/cloud/amazon/rds.py
 lib/ansible/modules/cloud/amazon/rds_param_group.py
 lib/ansible/modules/cloud/amazon/rds_subnet_group.py
 lib/ansible/modules/cloud/amazon/redshift.py