Repository: ansible/ansible

Issue:
Restarting EC2 instance causes modification of termination protection and the source_dest check
##### SUMMARY
When trying to restart an instance using the ec2 module, I noticed that the source/dest check was getting reset to 'true'.  This is what I was running: 
```    
     - name: restart instance
      local_action:
          module: ec2 
          region: "{{ ansible_ec2_placement_region }}"
          instance_ids: "{{ansible_ec2_instance_id}}"
          state: restarted
```

Looking at the code, I see that at least for the stopstart and restart processes, the code will modify both the source/dest check and termination protection.  This seems like a bug to me - just because I want to restart an instance doesn't mean I want to change its attributes.

##### ISSUE TYPE
 - Bug Report

##### COMPONENT NAME
ec2
##### ANSIBLE VERSION

```
ansible 2.5.3
  config file = None
  configured module search path = ['/Users/ykuperman/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /Users/ykuperman/venvs/ansible2/lib/python3.6/site-packages/ansible
  executable location = /Users/ykuperman/venvs/ansible2/bin/ansible
  python version = 3.6.3 (default, Nov 16 2017, 10:24:04) [GCC 4.2.1 Compatible Apple LLVM 7.0.2 (clang-700.1.81)]
```

##### CONFIGURATION
Nothing changed

##### OS / ENVIRONMENT
Running on MacOS, managing AWS Linux 2

##### STEPS TO REPRODUCE
1. set up an ec2 instance with source/dest check set to false
1. run a minimal restart using the example below

```
     - name: restart instance
      local_action:
          module: ec2 
          region: "{{ ansible_ec2_placement_region }}"
          instance_ids: "{{ansible_ec2_instance_id}}"
          state: restarted
```

##### EXPECTED RESULTS
Instance restarts with no modifications to the attributes

##### ACTUAL RESULTS
Instance restarts, but the source/dest check has changed


Discussion:
Files identified in the description:
* [lib/ansible/modules/cloud/amazon/ec2.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/amazon/ec2.py)

If these files are inaccurate, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @lwade @tgerla
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->

Resolving patch:
diff --git a/lib/ansible/modules/cloud/amazon/ec2.py b/lib/ansible/modules/cloud/amazon/ec2.py
--- a/lib/ansible/modules/cloud/amazon/ec2.py
+++ b/lib/ansible/modules/cloud/amazon/ec2.py
@@ -150,9 +150,9 @@
   source_dest_check:
     version_added: "1.6"
     description:
-      - Enable or Disable the Source/Destination checks (for NAT instances and Virtual Routers)
+      - Enable or Disable the Source/Destination checks (for NAT instances and Virtual Routers).
+        When initially creating an instance the EC2 API defaults this to True.
     type: bool
-    default: 'yes'
   termination_protection:
     version_added: "2.0"
     description:
@@ -1356,8 +1356,6 @@ def startstop_instances(module, ec2, instance_ids, state, instance_tags):
 
     wait = module.params.get('wait')
     wait_timeout = int(module.params.get('wait_timeout'))
-    source_dest_check = module.params.get('source_dest_check')
-    termination_protection = module.params.get('termination_protection')
     group_id = module.params.get('group_id')
     group_name = module.params.get('group')
     changed = False
@@ -1387,28 +1385,8 @@ def startstop_instances(module, ec2, instance_ids, state, instance_tags):
 
             warn_if_public_ip_assignment_changed(module, inst)
 
-            # Check "source_dest_check" attribute
-            try:
-                if inst.vpc_id is not None and inst.get_attribute('sourceDestCheck')['sourceDestCheck'] != source_dest_check:
-                    inst.modify_attribute('sourceDestCheck', source_dest_check)
-                    changed = True
-            except boto.exception.EC2ResponseError as exc:
-                # instances with more than one Elastic Network Interface will
-                # fail, because they have the sourceDestCheck attribute defined
-                # per-interface
-                if exc.code == 'InvalidInstanceID':
-                    for interface in inst.interfaces:
-                        if interface.source_dest_check != source_dest_check:
-                            ec2.modify_network_interface_attribute(interface.id, "sourceDestCheck", source_dest_check)
-                            changed = True
-                else:
-                    module.fail_json(msg='Failed to handle source_dest_check state for instance {0}, error: {1}'.format(inst.id, exc),
-                                     exception=traceback.format_exc())
-
-            # Check "termination_protection" attribute
-            if (inst.get_attribute('disableApiTermination')['disableApiTermination'] != termination_protection and termination_protection is not None):
-                inst.modify_attribute('disableApiTermination', termination_protection)
-                changed = True
+            changed = (check_source_dest_attr(module, inst, ec2) or
+                       check_termination_protection(module, inst) or changed)
 
             # Check security groups and if we're using ec2-vpc; ec2-classic security groups may not be modified
             if inst.vpc_id and group_name:
@@ -1488,8 +1466,6 @@ def restart_instances(module, ec2, instance_ids, state, instance_tags):
     this method will process the intersection of the two.
     """
 
-    source_dest_check = module.params.get('source_dest_check')
-    termination_protection = module.params.get('termination_protection')
     changed = False
     instance_dict_array = []
 
@@ -1516,28 +1492,8 @@ def restart_instances(module, ec2, instance_ids, state, instance_tags):
 
             warn_if_public_ip_assignment_changed(module, inst)
 
-            # Check "source_dest_check" attribute
-            try:
-                if inst.vpc_id is not None and inst.get_attribute('sourceDestCheck')['sourceDestCheck'] != source_dest_check:
-                    inst.modify_attribute('sourceDestCheck', source_dest_check)
-                    changed = True
-            except boto.exception.EC2ResponseError as exc:
-                # instances with more than one Elastic Network Interface will
-                # fail, because they have the sourceDestCheck attribute defined
-                # per-interface
-                if exc.code == 'InvalidInstanceID':
-                    for interface in inst.interfaces:
-                        if interface.source_dest_check != source_dest_check:
-                            ec2.modify_network_interface_attribute(interface.id, "sourceDestCheck", source_dest_check)
-                            changed = True
-                else:
-                    module.fail_json(msg='Failed to handle source_dest_check state for instance {0}, error: {1}'.format(inst.id, exc),
-                                     exception=traceback.format_exc())
-
-            # Check "termination_protection" attribute
-            if (inst.get_attribute('disableApiTermination')['disableApiTermination'] != termination_protection and termination_protection is not None):
-                inst.modify_attribute('disableApiTermination', termination_protection)
-                changed = True
+            changed = (check_source_dest_attr(module, inst, ec2) or
+                       check_termination_protection(module, inst) or changed)
 
             # Check instance state
             if inst.state != state:
@@ -1551,6 +1507,54 @@ def restart_instances(module, ec2, instance_ids, state, instance_tags):
     return (changed, instance_dict_array, instance_ids)
 
 
+def check_termination_protection(module, inst):
+    """
+    Check the instance disableApiTermination attribute.
+
+    module: Ansible module object
+    inst: EC2 instance object
+
+    returns: True if state changed None otherwise
+    """
+
+    termination_protection = module.params.get('termination_protection')
+
+    if (inst.get_attribute('disableApiTermination')['disableApiTermination'] != termination_protection and termination_protection is not None):
+        inst.modify_attribute('disableApiTermination', termination_protection)
+        return True
+
+
+def check_source_dest_attr(module, inst, ec2):
+    """
+    Check the instance sourceDestCheck attribute.
+
+    module: Ansible module object
+    inst: EC2 instance object
+
+    returns: True if state changed None otherwise
+    """
+
+    source_dest_check = module.params.get('source_dest_check')
+
+    if source_dest_check is not None:
+        try:
+            if inst.vpc_id is not None and inst.get_attribute('sourceDestCheck')['sourceDestCheck'] != source_dest_check:
+                inst.modify_attribute('sourceDestCheck', source_dest_check)
+                return True
+        except boto.exception.EC2ResponseError as exc:
+            # instances with more than one Elastic Network Interface will
+            # fail, because they have the sourceDestCheck attribute defined
+            # per-interface
+            if exc.code == 'InvalidInstanceID':
+                for interface in inst.interfaces:
+                    if interface.source_dest_check != source_dest_check:
+                        ec2.modify_network_interface_attribute(interface.id, "sourceDestCheck", source_dest_check)
+                        return True
+            else:
+                module.fail_json(msg='Failed to handle source_dest_check state for instance {0}, error: {1}'.format(inst.id, exc),
+                                 exception=traceback.format_exc())
+
+
 def warn_if_public_ip_assignment_changed(module, instance):
     # This is a non-modifiable attribute.
     assign_public_ip = module.params.get('assign_public_ip')
@@ -1591,7 +1595,7 @@ def main():
             private_ip=dict(),
             instance_profile_name=dict(),
             instance_ids=dict(type='list', aliases=['instance_id']),
-            source_dest_check=dict(type='bool', default=True),
+            source_dest_check=dict(type='bool', default=None),
             termination_protection=dict(type='bool', default=None),
             state=dict(default='present', choices=['present', 'absent', 'running', 'restarted', 'stopped']),
             instance_initiated_shutdown_behavior=dict(default=None, choices=['stop', 'terminate']),


Test patch: