# Buggy code
def create_launch_config(connection, module):
    name = module.params.get("name")
    vpc_id = module.params.get("vpc_id")
    try:
        region, ec2_url, aws_connect_kwargs = get_aws_connection_info(
            module, boto3=True
        )
        ec2_connection = boto3_conn(
            module, "client", "ec2", region, ec2_url, **aws_connect_kwargs
        )
        security_groups = get_ec2_security_group_ids_from_names(
            module.params.get("security_groups"),
            ec2_connection,
            vpc_id=vpc_id,
            boto3=True,
        )
    except botocore.exceptions.ClientError as e:
        module.fail_json(
            msg="Failed to get Security Group IDs",
            exception=traceback.format_exc(),
            **camel_dict_to_snake_dict(e.response),
        )
    except ValueError as e:
        module.fail_json(
            msg="Failed to get Security Group IDs", exception=traceback.format_exc()
        )
    user_data = module.params.get("user_data")
    user_data_path = module.params.get("user_data_path")
    volumes = module.params["volumes"]
    instance_monitoring = module.params.get("instance_monitoring")
    assign_public_ip = module.params.get("assign_public_ip")
    instance_profile_name = module.params.get("instance_profile_name")
    ebs_optimized = module.params.get("ebs_optimized")
    classic_link_vpc_id = module.params.get("classic_link_vpc_id")
    classic_link_vpc_security_groups = module.params.get(
        "classic_link_vpc_security_groups"
    )

    block_device_mapping = {}

    convert_list = [
        "image_id",
        "instance_type",
        "instance_type",
        "instance_id",
        "placement_tenancy",
        "key_name",
        "kernel_id",
        "ramdisk_id",
        "instance_profile_name",
        "spot_price",
    ]

    launch_config = snake_dict_to_camel_dict(
        dict(
            (k.capitalize(), str(v))
            for k, v in module.params.items()
            if v is not None and k in convert_list
        )
    )

    if user_data_path:
        try:
            with open(user_data_path, "r") as user_data_file:
                user_data = user_data_file.read()
        except IOError as e:
            module.fail_json(
                msg="Failed to open file for reading", exception=traceback.format_exc()
            )

    if volumes:
        for volume in volumes:
            if "device_name" not in volume:
                module.fail_json(msg="Device name must be set for volume")
            # Minimum volume size is 1GB. We'll use volume size explicitly set to 0 to be a signal not to create this volume
            if "volume_size" not in volume or int(volume["volume_size"]) > 0:
                block_device_mapping.update(create_block_device_meta(module, volume))

    try:
        launch_configs = connection.describe_launch_configurations(
            LaunchConfigurationNames=[name]
        ).get("LaunchConfigurations")
    except botocore.exceptions.ClientError as e:
        module.fail_json(
            msg="Failed to describe launch configuration by name",
            exception=traceback.format_exc(),
            **camel_dict_to_snake_dict(e.response),
        )

    changed = False
    result = {}

    launch_config["LaunchConfigurationName"] = name

    if security_groups is not None:
        launch_config["SecurityGroups"] = security_groups

    if classic_link_vpc_id is not None:
        launch_config["ClassicLinkVPCId"] = classic_link_vpc_id

    if instance_monitoring:
        launch_config["InstanceMonitoring"] = {"Enabled": instance_monitoring}

    if classic_link_vpc_security_groups is not None:
        launch_config["ClassicLinkVPCSecurityGroups"] = classic_link_vpc_security_groups

    if block_device_mapping:
        launch_config["BlockDeviceMappings"] = [block_device_mapping]

    if instance_profile_name is not None:
        launch_config["IamInstanceProfile"] = instance_profile_name

    if assign_public_ip is not None:
        launch_config["AssociatePublicIpAddress"] = assign_public_ip

    if user_data is not None:
        launch_config["UserData"] = user_data

    if ebs_optimized is not None:
        launch_config["EbsOptimized"] = ebs_optimized

    if len(launch_configs) == 0:
        try:
            connection.create_launch_configuration(**launch_config)
            launch_configs = connection.describe_launch_configurations(
                LaunchConfigurationNames=[name]
            ).get("LaunchConfigurations")
            changed = True
            if launch_configs:
                launch_config = launch_configs[0]
        except botocore.exceptions.ClientError as e:
            module.fail_json(
                msg="Failed to create launch configuration",
                exception=traceback.format_exc(),
                **camel_dict_to_snake_dict(e.response),
            )

    result = dict(
        (k, v)
        for k, v in launch_config.items()
        if k
        not in [
            "Connection",
            "CreatedTime",
            "InstanceMonitoring",
            "BlockDeviceMappings",
        ]
    )

    result["CreatedTime"] = to_text(launch_config.get("CreatedTime"))

    try:
        result["InstanceMonitoring"] = module.boolean(
            launch_config.get("InstanceMonitoring").get("Enabled")
        )
    except AttributeError:
        result["InstanceMonitoring"] = False

    result["BlockDeviceMappings"] = []

    for block_device_mapping in launch_config.get("BlockDeviceMappings", []):
        result["BlockDeviceMappings"].append(
            dict(
                device_name=block_device_mapping.get("DeviceName"),
                virtual_name=block_device_mapping.get("VirtualName"),
            )
        )
        if block_device_mapping.get("Ebs") is not None:
            result["BlockDeviceMappings"][-1]["ebs"] = dict(
                snapshot_id=block_device_mapping.get("Ebs").get("SnapshotId"),
                volume_size=block_device_mapping.get("Ebs").get("VolumeSize"),
            )

    if user_data_path:
        result["UserData"] = (
            "hidden"  # Otherwise, we dump binary to the user's terminal
        )

    return_object = {
        "Name": result.get("LaunchConfigurationName"),
        "CreatedTime": result.get("CreatedTime"),
        "ImageId": result.get("ImageId"),
        "Arn": result.get("LaunchConfigurationARN"),
        "SecurityGroups": result.get("SecurityGroups"),
        "InstanceType": result.get("InstanceType"),
        "Result": result,
    }

    module.exit_json(changed=changed, **camel_dict_to_snake_dict(return_object))


# Traceback (botocore.exceptions.ParamValidationError)
ansible-playbook 2.5.0 (devel adc533804a) last updated 2017/09/12 14:51:23 (GMT -200)
config file = /etc/ansible/ansible.cfg
configured module search path = [u'/home/gustavo/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
ansible python module location = /home/gustavo/Documents/Tools/Ansible/source/ansible/lib/ansible
executable location = /home/gustavo/Documents/Tools/Ansible/source/ansible/bin/ansible-playbook
python version = 2.7.12 (default, Nov 19 2016, 06:48:10) [GCC 5.4.0 20160609]
Using /etc/ansible/ansible.cfg as config file
setting up inventory plugins
Set default localhost to localhost
Parsed /etc/ansible/hosts inventory source with ini plugin
[WARNING]: Found both group and host with same name: nexus

[WARNING]: Found both group and host with same name: sonar

[WARNING]: Found both group and host with same name: ci-server

[WARNING]: Found both group and host with same name: rundeck

Loading callback plugin default of type stdout, v2.0 from /home/gustavo/Documents/Tools/Ansible/source/ansible/lib/ansible/plugins/callback/__init__.pyc

PLAYBOOK: bug.yml ****************************************************************************************************************************************************************************************************************************
clear: <built-in method clear of dict object at 0x7fb9dfab26e0>
copy: <built-in method copy of dict object at 0x7fb9dfab26e0>
fromkeys: <built-in method fromkeys of type object at 0x906f00>
get: <built-in method get of dict object at 0x7fb9dfab26e0>
has_key: <built-in method has_key of dict object at 0x7fb9dfab26e0>
items: <built-in method items of dict object at 0x7fb9dfab26e0>
iteritems: <built-in method iteritems of dict object at 0x7fb9dfab26e0>
iterkeys: <built-in method iterkeys of dict object at 0x7fb9dfab26e0>
itervalues: <built-in method itervalues of dict object at 0x7fb9dfab26e0>
keys: <built-in method keys of dict object at 0x7fb9dfab26e0>
pop: <built-in method pop of dict object at 0x7fb9dfab26e0>
popitem: <built-in method popitem of dict object at 0x7fb9dfab26e0>
setdefault: <built-in method setdefault of dict object at 0x7fb9dfab26e0>
update: <built-in method update of dict object at 0x7fb9dfab26e0>
values: <built-in method values of dict object at 0x7fb9dfab26e0>
viewitems: <built-in method viewitems of dict object at 0x7fb9dfab26e0>
viewkeys: <built-in method viewkeys of dict object at 0x7fb9dfab26e0>
viewvalues: <built-in method viewvalues of dict object at 0x7fb9dfab26e0>
1 plays in bug.yml

PLAY [localhost] *****************************************************************************************************************************************************************************************************************************
META: ran handlers

TASK [ec2_lc] ********************************************************************************************************************************************************************************************************************************
task path: /home/gustavo/Documents/enkel/clientes/smiles/agile/playbooks/ecs/smiles-ansible-role-ec2-asg/bug.yml:5
Using module file /home/gustavo/Documents/Tools/Ansible/source/ansible/lib/ansible/modules/cloud/amazon/ec2_lc.py
<localhost> ESTABLISH LOCAL CONNECTION FOR USER: gustavo
<localhost> EXEC /bin/sh -c 'echo ~ &amp;&amp; sleep 0'
<localhost> EXEC /bin/sh -c '( umask 77 &amp;&amp; mkdir -p "` echo /home/gustavo/.ansible/tmp/ansible-tmp-1505301913.36-244577228156109 `" &amp;&amp; echo ansible-tmp-1505301913.36-244577228156109="` echo /home/gustavo/.ansible/tmp/ansible-tmp-1505301913.36-244577228156109 `" ) &amp;&amp; sleep 0'
<localhost> PUT /tmp/tmp00tzYL TO /home/gustavo/.ansible/tmp/ansible-tmp-1505301913.36-244577228156109/ec2_lc.py
<localhost> EXEC /bin/sh -c 'chmod u+x /home/gustavo/.ansible/tmp/ansible-tmp-1505301913.36-244577228156109/ /home/gustavo/.ansible/tmp/ansible-tmp-1505301913.36-244577228156109/ec2_lc.py &amp;&amp; sleep 0'
<localhost> EXEC /bin/sh -c '/usr/bin/python3 /home/gustavo/.ansible/tmp/ansible-tmp-1505301913.36-244577228156109/ec2_lc.py; rm -rf "/home/gustavo/.ansible/tmp/ansible-tmp-1505301913.36-244577228156109/" > /dev/null 2>&amp;1 &amp;&amp; sleep 0'
The full traceback is:
Traceback (most recent call last):
File "/tmp/ansible_74jhiu5w/ansible_module_ec2_lc.py", line 428, in <module>
main()
File "/tmp/ansible_74jhiu5w/ansible_module_ec2_lc.py", line 422, in main
create_launch_config(connection, module)
File "/tmp/ansible_74jhiu5w/ansible_module_ec2_lc.py", line 319, in create_launch_config
connection.create_launch_configuration(**launch_config)
File "/usr/local/lib/python3.5/dist-packages/botocore/client.py", line 312, in _api_call
return self._make_api_call(operation_name, kwargs)
File "/usr/local/lib/python3.5/dist-packages/botocore/client.py", line 575, in _make_api_call
api_params, operation_model, context=request_context)
File "/usr/local/lib/python3.5/dist-packages/botocore/client.py", line 630, in _convert_to_request_dict
api_params, operation_model)
File "/usr/local/lib/python3.5/dist-packages/botocore/validate.py", line 291, in serialize_to_request
raise ParamValidationError(report=report.generate_report())
botocore.exceptions.ParamValidationError: Parameter validation failed:
Unknown parameter in input: "InstanceProfileName", must be one of: LaunchConfigurationName, ImageId, KeyName, SecurityGroups, ClassicLinkVPCId, ClassicLinkVPCSecurityGroups, UserData, InstanceId, InstanceType, KernelId, RamdiskId, BlockDeviceMappings, InstanceMonitoring, SpotPrice, IamInstanceProfile, EbsOptimized, AssociatePublicIpAddress, PlacementTenancy

fatal: [localhost]: FAILED! => {
"changed": false,
"failed": true,
"module_stderr": "Traceback (most recent call last):\n  File \"/tmp/ansible_74jhiu5w/ansible_module_ec2_lc.py\", line 428, in <module>\n    main()\n  File \"/tmp/ansible_74jhiu5w/ansible_module_ec2_lc.py\", line 422, in main\n    create_launch_config(connection, module)\n  File \"/tmp/ansible_74jhiu5w/ansible_module_ec2_lc.py\", line 319, in create_launch_config\n    connection.create_launch_configuration(**launch_config)\n  File \"/usr/local/lib/python3.5/dist-packages/botocore/client.py\", line 312, in _api_call\n    return self._make_api_call(operation_name, kwargs)\n  File \"/usr/local/lib/python3.5/dist-packages/botocore/client.py\", line 575, in _make_api_call\n    api_params, operation_model, context=request_context)\n  File \"/usr/local/lib/python3.5/dist-packages/botocore/client.py\", line 630, in _convert_to_request_dict\n    api_params, operation_model)\n  File \"/usr/local/lib/python3.5/dist-packages/botocore/validate.py\", line 291, in serialize_to_request\n    raise ParamValidationError(report=report.generate_report())\nbotocore.exceptions.ParamValidationError: Parameter validation failed:\nUnknown parameter in input: \"InstanceProfileName\", must be one of: LaunchConfigurationName, ImageId, KeyName, SecurityGroups, ClassicLinkVPCId, ClassicLinkVPCSecurityGroups, UserData, InstanceId, InstanceType, KernelId, RamdiskId, BlockDeviceMappings, InstanceMonitoring, SpotPrice, IamInstanceProfile, EbsOptimized, AssociatePublicIpAddress, PlacementTenancy\n",
"module_stdout": "",
"msg": "MODULE FAILURE",
"rc": 0
}
to retry, use: --limit @/home/gustavo/Documents/enkel/clientes/smiles/agile/playbooks/ecs/smiles-ansible-role-ec2-asg/bug.retry

PLAY RECAP ***********************************************************************************************************************************************************************************************************************************
localhost                  : ok=0    changed=0    unreachable=0    failed=1

# Fixed code
def create_launch_config(connection, module):
    name = module.params.get("name")
    vpc_id = module.params.get("vpc_id")
    try:
        region, ec2_url, aws_connect_kwargs = get_aws_connection_info(
            module, boto3=True
        )
        ec2_connection = boto3_conn(
            module, "client", "ec2", region, ec2_url, **aws_connect_kwargs
        )
        security_groups = get_ec2_security_group_ids_from_names(
            module.params.get("security_groups"),
            ec2_connection,
            vpc_id=vpc_id,
            boto3=True,
        )
    except botocore.exceptions.ClientError as e:
        module.fail_json(
            msg="Failed to get Security Group IDs",
            exception=traceback.format_exc(),
            **camel_dict_to_snake_dict(e.response),
        )
    except ValueError as e:
        module.fail_json(
            msg="Failed to get Security Group IDs", exception=traceback.format_exc()
        )
    user_data = module.params.get("user_data")
    user_data_path = module.params.get("user_data_path")
    volumes = module.params["volumes"]
    instance_monitoring = module.params.get("instance_monitoring")
    assign_public_ip = module.params.get("assign_public_ip")
    instance_profile_name = module.params.get("instance_profile_name")
    ebs_optimized = module.params.get("ebs_optimized")
    classic_link_vpc_id = module.params.get("classic_link_vpc_id")
    classic_link_vpc_security_groups = module.params.get(
        "classic_link_vpc_security_groups"
    )

    block_device_mapping = {}

    convert_list = [
        "image_id",
        "instance_type",
        "instance_type",
        "instance_id",
        "placement_tenancy",
        "key_name",
        "kernel_id",
        "ramdisk_id",
        "spot_price",
    ]

    launch_config = snake_dict_to_camel_dict(
        dict(
            (k.capitalize(), str(v))
            for k, v in module.params.items()
            if v is not None and k in convert_list
        )
    )

    if user_data_path:
        try:
            with open(user_data_path, "r") as user_data_file:
                user_data = user_data_file.read()
        except IOError as e:
            module.fail_json(
                msg="Failed to open file for reading", exception=traceback.format_exc()
            )

    if volumes:
        for volume in volumes:
            if "device_name" not in volume:
                module.fail_json(msg="Device name must be set for volume")
            # Minimum volume size is 1GB. We'll use volume size explicitly set to 0 to be a signal not to create this volume
            if "volume_size" not in volume or int(volume["volume_size"]) > 0:
                block_device_mapping.update(create_block_device_meta(module, volume))

    try:
        launch_configs = connection.describe_launch_configurations(
            LaunchConfigurationNames=[name]
        ).get("LaunchConfigurations")
    except botocore.exceptions.ClientError as e:
        module.fail_json(
            msg="Failed to describe launch configuration by name",
            exception=traceback.format_exc(),
            **camel_dict_to_snake_dict(e.response),
        )

    changed = False
    result = {}

    launch_config["LaunchConfigurationName"] = name

    if security_groups is not None:
        launch_config["SecurityGroups"] = security_groups

    if classic_link_vpc_id is not None:
        launch_config["ClassicLinkVPCId"] = classic_link_vpc_id

    if instance_monitoring:
        launch_config["InstanceMonitoring"] = {"Enabled": instance_monitoring}

    if classic_link_vpc_security_groups is not None:
        launch_config["ClassicLinkVPCSecurityGroups"] = classic_link_vpc_security_groups

    if block_device_mapping:
        launch_config["BlockDeviceMappings"] = [block_device_mapping]

    if instance_profile_name is not None:
        launch_config["IamInstanceProfile"] = instance_profile_name

    if assign_public_ip is not None:
        launch_config["AssociatePublicIpAddress"] = assign_public_ip

    if user_data is not None:
        launch_config["UserData"] = user_data

    if ebs_optimized is not None:
        launch_config["EbsOptimized"] = ebs_optimized

    if len(launch_configs) == 0:
        try:
            connection.create_launch_configuration(**launch_config)
            launch_configs = connection.describe_launch_configurations(
                LaunchConfigurationNames=[name]
            ).get("LaunchConfigurations")
            changed = True
            if launch_configs:
                launch_config = launch_configs[0]
        except botocore.exceptions.ClientError as e:
            module.fail_json(
                msg="Failed to create launch configuration",
                exception=traceback.format_exc(),
                **camel_dict_to_snake_dict(e.response),
            )

    result = dict(
        (k, v)
        for k, v in launch_config.items()
        if k
        not in [
            "Connection",
            "CreatedTime",
            "InstanceMonitoring",
            "BlockDeviceMappings",
        ]
    )

    result["CreatedTime"] = to_text(launch_config.get("CreatedTime"))

    try:
        result["InstanceMonitoring"] = module.boolean(
            launch_config.get("InstanceMonitoring").get("Enabled")
        )
    except AttributeError:
        result["InstanceMonitoring"] = False

    result["BlockDeviceMappings"] = []

    for block_device_mapping in launch_config.get("BlockDeviceMappings", []):
        result["BlockDeviceMappings"].append(
            dict(
                device_name=block_device_mapping.get("DeviceName"),
                virtual_name=block_device_mapping.get("VirtualName"),
            )
        )
        if block_device_mapping.get("Ebs") is not None:
            result["BlockDeviceMappings"][-1]["ebs"] = dict(
                snapshot_id=block_device_mapping.get("Ebs").get("SnapshotId"),
                volume_size=block_device_mapping.get("Ebs").get("VolumeSize"),
            )

    if user_data_path:
        result["UserData"] = (
            "hidden"  # Otherwise, we dump binary to the user's terminal
        )

    return_object = {
        "Name": result.get("LaunchConfigurationName"),
        "CreatedTime": result.get("CreatedTime"),
        "ImageId": result.get("ImageId"),
        "Arn": result.get("LaunchConfigurationARN"),
        "SecurityGroups": result.get("SecurityGroups"),
        "InstanceType": result.get("InstanceType"),
        "Result": result,
    }

    module.exit_json(changed=changed, **camel_dict_to_snake_dict(return_object))