2016-04-06 13:37:52 +00:00
# Guidelines for AWS modules
2015-06-19 02:40:56 +00:00
2017-08-23 14:34:30 +00:00
The Ansible AWS modules and these guidelines are maintained by the Ansible AWS Working Group. For
2017-12-06 20:36:25 +00:00
further information see
[the AWS working group community page ](https://github.com/ansible/community/tree/master/group-aws ).
2018-08-22 19:53:30 +00:00
If you are planning to contribute AWS modules to Ansible then getting in touch with the working
2017-08-23 14:34:30 +00:00
group will be a good way to start, especially because a similar module may already be under
development.
2016-04-06 13:37:52 +00:00
2017-08-23 14:34:30 +00:00
## Choose Boto3 not Boto
2016-04-06 13:37:52 +00:00
2017-08-23 14:34:30 +00:00
Starting from Ansible 2.0, it has been required that all new AWS modules are written to use boto3.
Please do not add new dependencies on the old boto library.
Prior to 2.0, modules may have been written in boto or boto3. The effort to port all modules to
boto3 has begun. From Ansible 2.4 it is permissible for modules which previously required boto to
2018-03-02 19:03:12 +00:00
start to migrate to boto3 in order to deliver the functionality previously supported with boto and the
2017-08-23 14:34:30 +00:00
boto dependency can be deleted.
2016-04-06 13:37:52 +00:00
2018-03-02 19:03:12 +00:00
From 2.6, all new modules should use AnsibleAWSModule as a base, or have a documented reason not
to. Using AnsibleAWSModule greatly simplifies exception handling and library management, reducing
the amount of boilerplate code.
## Porting code to AnsibleAWSModule
Change
```
from ansible.module_utils.basic import AnsibleModule
...
module = AnsibleModule(...)
```
to
```
from ansible.module_utils.aws.core import AnsibleAWSModule
...
module = AnsibleAWSModule(...)
```
Few other changes are required. One possible issue that you might encounter is that AnsibleAWSModule
does not inherit methods from AnsibleModule by default, but most useful methods
are included. If you do find an issue, please raise a bug report.
2018-04-26 12:26:03 +00:00
When porting, keep in mind that AnsibleAWSModule also will add the default ec2
argument spec by default. In pre-port modules, you should see common arguments
specfied with:
```
def main():
argument_spec = ec2_argument_spec()
argument_spec.update(dict(
state=dict(default='present', choices=['present', 'absent', 'enabled', 'disabled']),
name=dict(default='default'),
# ... and so on ...
))
module = AnsibleModule(argument_spec=argument_spec, ...)
# can be replaced with
def main():
argument_spec = dict(
state=dict(default='present', choices=['present', 'absent', 'enabled', 'disabled']),
name=dict(default='default'),
# ... and so on ...
)
module = AnsibleAWSModule(argument_spec=argument_spec, ...)
```
2016-04-06 13:37:52 +00:00
## Bug fixing
2017-08-23 14:34:30 +00:00
Bug fixes to code that relies on boto will still be accepted. When possible, the code should be
ported to use boto3.
2016-04-06 13:37:52 +00:00
## Naming your module
2015-06-19 02:40:56 +00:00
2017-08-23 14:34:30 +00:00
Base the name of the module on the part of AWS that you actually use. (A good rule of thumb is to
take whatever module you use with boto as a starting point). Don't further abbreviate names - if
something is a well known abbreviation due to it being a major component of AWS, that's fine, but
don't create new ones independently (e.g. VPC, ELB, and similar abbreviations are fine).
2015-06-19 02:40:56 +00:00
2017-08-23 14:34:30 +00:00
Unless the name of your service is quite unique, please consider using "aws_" as a prefix. For
example "aws_lambda".
2015-06-19 02:40:56 +00:00
2016-04-06 13:37:52 +00:00
## Adding new features
2017-08-23 14:34:30 +00:00
Try to keep backward compatibility with relatively recent versions of boto3. That means that if you
want to implement some functionality that uses a new feature of boto3, it should only fail if that
feature actually needs to be run, with a message stating the missing feature and minimum required
version of boto3.
2015-06-19 02:40:56 +00:00
2017-08-23 14:34:30 +00:00
Use feature testing (e.g. `hasattr('boto3.module', 'shiny_new_method')` ) to check whether boto3
supports a feature rather than version checking
2015-06-19 02:40:56 +00:00
2016-04-06 13:37:52 +00:00
e.g. from the `ec2` module:
```python
if boto_supports_profile_name_arg(ec2):
params['instance_profile_name'] = instance_profile_name
else:
if instance_profile_name is not None:
module.fail_json(msg="instance_profile_name parameter requires boto version 2.5.0 or higher")
2015-06-19 02:40:56 +00:00
```
2016-04-06 13:37:52 +00:00
2017-08-23 14:34:30 +00:00
## Using botocore and boto3
2016-04-06 13:37:52 +00:00
### Importing
2017-08-23 14:34:30 +00:00
The `ansible.module_utils.ec2` module and `ansible.module_utils.core.aws` modules will both
automatically import boto3 and botocore. If boto3 is missing from the system then the variable
2018-03-02 19:03:12 +00:00
`HAS_BOTO3` will be set to false. Normally, this means that modules don't need to import
boto3 directly. There is no need to check `HAS_BOTO3` when using AnsibleAWSModule
2017-11-06 20:05:32 +00:00
as the module does that check.
2017-08-23 14:34:30 +00:00
2016-04-06 13:37:52 +00:00
```python
2018-03-02 19:03:12 +00:00
from ansible.module_utils.aws.core import AnsibleAWSModule
2015-06-19 02:40:56 +00:00
try:
2018-03-02 19:03:12 +00:00
import botocore
2015-06-19 02:40:56 +00:00
except ImportError:
2018-03-02 19:03:12 +00:00
pass # handled by AnsibleAWSModule
2017-11-06 20:05:32 +00:00
```
or
```python
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ec2 import HAS_BOTO3
2018-03-02 19:03:12 +00:00
try:
import botocore
except ImportError:
pass # handled by imported HAS_BOTO3
2015-06-19 02:40:56 +00:00
2016-04-06 13:37:52 +00:00
def main():
2015-06-19 02:40:56 +00:00
2016-04-12 22:10:41 +00:00
if not HAS_BOTO3:
2017-08-23 14:34:30 +00:00
module.fail_json(msg='boto3 and botocore are required for this module')
2015-06-19 02:40:56 +00:00
```
2016-04-06 13:37:52 +00:00
#### boto and boto3 combined
2018-03-02 19:03:12 +00:00
Modules should be ported to use boto3 rather than use both boto and boto3.
2016-04-06 13:37:52 +00:00
2015-06-19 02:40:56 +00:00
2016-04-06 13:37:52 +00:00
### Connecting to AWS
2018-03-02 01:40:01 +00:00
AnsibleAWSModule provides the `resource` and `client` helper methods for obtaining boto3 connections.
These handle some of the more esoteric connection options, such as security tokens and boto profiles.
2016-04-06 13:37:52 +00:00
2018-03-02 01:40:01 +00:00
If using the basic AnsibleModule then you should use `get_aws_connection_info` and then `boto3_conn`
to connect to AWS as these handle the same range of connection options.
2016-04-06 13:37:52 +00:00
2018-03-02 01:40:01 +00:00
These helpers also for missing profiles or a region not set when it needs to be, so you don't have to.
An example of connecting to ec2 is shown below. Note that unlike boto there is no `NoAuthHandlerFound`
exception handling like in boto. Instead, an `AuthFailure` exception will be thrown when you use the
connection. To ensure that authorization, parameter validation and permissions errors are all caught,
you should catch `ClientError` and `BotoCoreError` exceptions with every boto3 connection call.
See exception handling.
```python
module.client('ec2')
```
or for the higher level ec2 resource:
```python
module.resource('ec2')
```
An example of the older style connection used for modules based on AnsibleModule rather than AnsibleAWSModule:
```python
region, ec2_url, aws_connect_params = get_aws_connection_info(module, boto3=True)
connection = boto3_conn(module, conn_type='client', resource='ec2', region=region, endpoint=ec2_url, **aws_connect_params)
```
2015-06-19 02:40:56 +00:00
2016-04-06 13:37:52 +00:00
```python
2018-03-02 19:03:12 +00:00
region, ec2_url, aws_connect_params = get_aws_connection_info(module, boto3=True)
connection = boto3_conn(module, conn_type='client', resource='ec2', region=region, endpoint=ec2_url, **aws_connect_params)
2015-06-19 02:40:56 +00:00
```
2016-04-06 13:37:52 +00:00
2018-03-02 01:40:01 +00:00
### Common Documentation Fragments for Connection Parameters
2018-04-22 07:15:16 +00:00
There are two [common documentation fragments ](https://docs.ansible.com/ansible/latest/dev_guide/developing_modules_documenting.html#documentation-fragments )
2018-03-02 01:40:01 +00:00
that should be included into almost all AWS modules:
* `aws` - contains the common boto connection parameters
* `ec2` - contains the common region parameter required for many AWS modules
2016-04-06 13:37:52 +00:00
2018-03-02 01:40:01 +00:00
These fragments should be used rather than re-documenting these properties to ensure consistency
and that the more esoteric connection options are documented. e.g.
2016-04-06 13:37:52 +00:00
```python
2018-03-02 01:40:01 +00:00
DOCUMENTATION = '''
module: my_module
...
requirements: [ 'botocore', 'boto3' ]
extends_documentation_fragment:
- aws
- ec2
'''
2015-06-19 02:40:56 +00:00
```
2018-03-02 19:03:12 +00:00
### Exception Handling
2017-08-23 14:34:30 +00:00
You should wrap any boto3 or botocore call in a try block. If an exception is thrown, then there
are a number of possibilities for handling it.
2018-06-12 16:15:16 +00:00
* Catch the general `ClientError` or look for a specific error code with
`is_boto3_error_code` .
* Use aws_module.fail_json_aws() to report the module failure in a standard way
* Retry using AWSRetry
* Use fail_json() to report the failure without using `ansible.module_utils.aws.core`
* Do something custom in the case where you know how to handle the exception
2017-08-23 14:34:30 +00:00
2018-06-18 12:22:50 +00:00
For more information on botocore exception handling see [the botocore error documentation ](https://botocore.readthedocs.io/en/latest/client_upgrades.html#error-handling ).
2017-08-23 14:34:30 +00:00
2018-06-12 16:15:16 +00:00
### Using is_boto3_error_code
To use `ansible.module_utils.aws.core.is_boto3_error_code` to catch a single
AWS error code, call it in place of `ClientError` in your except clauses. In
this case, *only* the `InvalidGroup.NotFound` error code will be caught here,
and any other error will be raised for handling elsewhere in the program.
```python
try:
return connection.describe_security_groups(**kwargs)
except is_boto3_error_code('InvalidGroup.NotFound'):
return {'SecurityGroups': []}
```
#### Using fail_json_aws()
2017-08-23 14:34:30 +00:00
2017-11-01 02:34:34 +00:00
In the AnsibleAWSModule there is a special method, `module.fail_json_aws()` for nice reporting of
2017-08-23 14:34:30 +00:00
exceptions. Call this on your exception and it will report the error together with a traceback for
use in Ansible verbose mode.
2018-03-02 19:03:12 +00:00
You should use the AnsibleAWSModule for all new modules, unless not possible. If adding significant
amounts of exception handling to existing modules, we recommend migrating the module to use AnsibleAWSModule
(there are very few changes required to do this)
2017-08-23 14:34:30 +00:00
```python
2018-02-27 14:22:00 +00:00
from ansible.module_utils.aws.core import AnsibleAWSModule
2017-08-23 14:34:30 +00:00
# Set up module parameters
...
# Connect to AWS
...
# Make a call to AWS
2017-11-06 20:05:32 +00:00
name = module.params.get['name']
2017-08-23 14:34:30 +00:00
try:
2017-11-06 20:05:32 +00:00
result = connection.describe_frooble(FroobleName=name)
except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e:
module.fail_json_aws(e, msg="Couldn't obtain frooble %s" % name)
2017-08-23 14:34:30 +00:00
```
Note that it should normally be acceptable to catch all normal exceptions here, however if you
expect anything other than botocore exceptions you should test everything works as expected.
If you need to perform an action based on the error boto3 returned, use the error code.
2016-04-06 13:37:52 +00:00
2017-08-23 14:34:30 +00:00
```python
# Make a call to AWS
2017-11-06 20:05:32 +00:00
name = module.params.get['name']
2017-08-23 14:34:30 +00:00
try:
2017-11-06 20:05:32 +00:00
result = connection.describe_frooble(FroobleName=name)
except botocore.exceptions.ClientError as e:
if e.response['Error']['Code'] == 'FroobleNotFound':
2017-08-23 14:34:30 +00:00
return None
else:
2017-11-06 20:05:32 +00:00
module.fail_json_aws(e, msg="Couldn't obtain frooble %s" % name)
except botocore.exceptions.BotoCoreError as e:
module.fail_json_aws(e, msg="Couldn't obtain frooble %s" % name)
2017-08-23 14:34:30 +00:00
```
2016-04-06 13:37:52 +00:00
2017-08-23 14:34:30 +00:00
#### using fail_json() and avoiding ansible.module_utils.aws.core
Boto3 provides lots of useful information when an exception is thrown so pass this to the user
along with the message.
2016-05-11 10:33:52 +00:00
2016-04-06 13:37:52 +00:00
```python
2017-11-06 20:05:32 +00:00
from ansible.module_utils.ec2 import HAS_BOTO3
2016-04-06 13:37:52 +00:00
try:
2017-11-06 20:05:32 +00:00
import botocore
2016-04-06 13:37:52 +00:00
except ImportError:
2017-11-06 20:05:32 +00:00
pass # caught by imported HAS_BOTO3
2016-04-06 13:37:52 +00:00
# Connect to AWS
...
# Make a call to AWS
2017-11-06 20:05:32 +00:00
name = module.params.get['name']
2016-04-06 13:37:52 +00:00
try:
2017-11-06 20:05:32 +00:00
result = connection.describe_frooble(FroobleName=name)
except botocore.exceptions.ClientError as e:
module.fail_json(msg="Couldn't obtain frooble %s: %s" % (name, str(e)),
exception=traceback.format_exc(),
2017-03-23 18:08:02 +00:00
**camel_dict_to_snake_dict(e.response))
2016-05-11 10:33:52 +00:00
```
2017-11-06 20:05:32 +00:00
Note: we use `str(e)` rather than `e.message` as the latter doesn't
work with python3
2016-05-11 10:33:52 +00:00
If you need to perform an action based on the error boto3 returned, use the error code.
```python
# Make a call to AWS
2017-11-06 20:05:32 +00:00
name = module.params.get['name']
2016-05-11 10:33:52 +00:00
try:
2017-11-06 20:05:32 +00:00
result = connection.describe_frooble(FroobleName=name)
except botocore.exceptions.ClientError as e:
if e.response['Error']['Code'] == 'FroobleNotFound':
2016-05-11 10:33:52 +00:00
return None
else:
2017-11-06 20:05:32 +00:00
module.fail_json(msg="Couldn't obtain frooble %s: %s" % (name, str(e)),
exception=traceback.format_exc(),
2017-03-23 18:08:02 +00:00
**camel_dict_to_snake_dict(e.response))
2017-11-06 20:05:32 +00:00
except botocore.exceptions.BotoCoreError as e:
module.fail_json_aws(e, msg="Couldn't obtain frooble %s" % name)
2015-06-19 02:40:56 +00:00
```
2017-09-12 13:29:04 +00:00
### API throttling and pagination
For methods that return a lot of results, boto3 often provides
[paginators ](http://boto3.readthedocs.io/en/latest/guide/paginators.html ). If the method
you're calling has `NextToken` or `Marker` parameters, you should probably
check whether a paginator exists (the top of each boto3 service reference page has a link
to Paginators, if the service has any). To use paginators, obtain a paginator object,
call `paginator.paginate` with the appropriate arguments and then call `build_full_result` .
Any time that you are calling the AWS API a lot, you may experience API throttling,
and there is an `AWSRetry` decorator that can be used to ensure backoff. Because
exception handling could interfere with the retry working properly (as AWSRetry needs to
catch throttling exceptions to work correctly), you'd need to provide a backoff function
and then put exception handling around the backoff function.
You can use `exponential_backoff` or `jittered_backoff` strategies - see
2018-03-27 08:55:25 +00:00
the [cloud module_utils ](/lib/ansible/module_utils/cloud.py )
2017-09-12 13:29:04 +00:00
and [AWS Architecture blog ](https://www.awsarchitectureblog.com/2015/03/backoff.html )
for more details.
The combination of these two approaches is then
```
2017-11-06 20:05:32 +00:00
@AWSRetry .exponential_backoff(retries=5, delay=5)
2017-09-12 13:29:04 +00:00
def describe_some_resource_with_backoff(client, **kwargs):
paginator = client.get_paginator('describe_some_resource')
return paginator.paginate(**kwargs).build_full_result()['SomeResource']
def describe_some_resource(client, module):
filters = ansible_dict_to_boto3_filter_list(module.params['filters'])
try:
return describe_some_resource_with_backoff(client, Filters=filters)
except botocore.exceptions.ClientError as e:
module.fail_json_aws(e, msg="Could not describe some resource")
```
2017-11-06 20:05:32 +00:00
If the underlying `describe_some_resources` API call throws a `ResourceNotFound`
exception, `AWSRetry` takes this as a cue to retry until it's not thrown (this
is so that when creating a resource, we can just retry until it exists).
2017-12-06 20:36:25 +00:00
To handle authorization failures or parameter validation errors in
2017-11-06 20:05:32 +00:00
`describe_some_resource_with_backoff` , where we just want to return `None` if
the resource doesn't exist and not retry, we need:
```
@AWSRetry .exponential_backoff(retries=5, delay=5)
def describe_some_resource_with_backoff(client, **kwargs):
try:
return client.describe_some_resource(ResourceName=kwargs['name'])['Resources']
except botocore.exceptions.ClientError as e:
if e.response['Error']['Code'] == 'ResourceNotFound':
return None
else:
raise
except BotoCoreError as e:
raise
def describe_some_resource(client, module):
name = module.params.get['name']
try:
return describe_some_resource_with_backoff(client, name=name)
except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e:
module.fail_json_aws(e, msg="Could not describe resource %s" % name)
```
2018-04-26 12:26:03 +00:00
To make use of AWSRetry easier, it can now be wrapped around a client returned
by `AnsibleAWSModule` . any call from a client. To add retries to a client,
create a client:
```
module.client('ec2', retry_decorator=AWSRetry.jittered_backoff(retries=10))
```
Any calls from that client can be made to use the decorator passed at call-time
using the `aws_retry` argument. By default, no retries are used.
```
ec2 = module.client('ec2', retry_decorator=AWSRetry.jittered_backoff(retries=10))
ec2.describe_instances(InstanceIds=['i-123456789'], aws_retry=True)
# equivalent with normal AWSRetry
@AWSRetry .jittered_backoff(retries=10)
def describe_instances(client, **kwargs):
return ec2.describe_instances(**kwargs)
describe_instances(module.client('ec2'), InstanceIds=['i-123456789'])
```
The call will be retried the specified number of times, so the calling functions
don't need to be wrapped in the backoff decorator.
2016-05-23 14:31:41 +00:00
### Returning Values
2017-08-23 14:34:30 +00:00
When you make a call using boto3, you will probably get back some useful information that you
should return in the module. As well as information related to the call itself, you will also have
some response metadata. It is OK to return this to the user as well as they may find it useful.
2016-05-23 14:31:41 +00:00
2017-08-23 14:34:30 +00:00
Boto3 returns all values CamelCased. Ansible follows Python standards for variable names and uses
snake_case. There is a helper function in module_utils/ec2.py called `camel_dict_to_snake_dict`
that allows you to easily convert the boto3 response to snake_case.
2016-05-23 14:31:41 +00:00
2017-08-23 14:34:30 +00:00
You should use this helper function and avoid changing the names of values returned by Boto3.
2017-12-06 20:36:25 +00:00
E.g. if boto3 returns a value called 'SecretAccessKey' do not change it to 'AccessKey'.
2016-05-23 14:31:41 +00:00
```python
# Make a call to AWS
result = connection.aws_call()
# Return the result to the user
module.exit_json(changed=True, **camel_dict_to_snake_dict(result))
```
2016-12-22 12:22:40 +00:00
### Dealing with IAM JSON policy
2017-08-23 14:34:30 +00:00
If your module accepts IAM JSON policies then set the type to 'json' in the module spec. For
example:
2016-12-22 12:22:40 +00:00
```python
argument_spec.update(
dict(
policy=dict(required=False, default=None, type='json'),
)
)
```
2017-08-23 14:34:30 +00:00
Note that AWS is unlikely to return the policy in the same order that is was submitted. Therefore,
2018-03-09 21:19:55 +00:00
use the `compare_policies` helper function which handles this variance.
`compare_policies` takes two dictionaries, recursively sorts and makes them hashable for comparison
and returns True if they are different.
2016-12-22 12:22:40 +00:00
```python
2018-03-09 21:19:55 +00:00
from ansible.module_utils.ec2 import compare_policies
import json
......
2016-12-22 12:22:40 +00:00
# Get the policy from AWS
2018-03-09 21:19:55 +00:00
current_policy = json.loads(aws_object.get_policy())
user_policy = json.loads(module.params.get('policy'))
2016-12-22 12:22:40 +00:00
2018-03-09 21:19:55 +00:00
# Compare the user submitted policy to the current policy ignoring order
if compare_policies(user_policy, current_policy):
2016-12-22 12:22:40 +00:00
# Update the policy
aws_object.set_policy(user_policy)
2018-03-09 21:19:55 +00:00
else:
# Nothing to do
pass
2016-12-22 12:22:40 +00:00
```
2017-05-11 06:39:51 +00:00
### Dealing with tags
AWS has a concept of resource tags. Usually the boto3 API has separate calls for tagging and
untagging a resource. For example, the ec2 API has a create_tags and delete_tags call.
2017-08-23 14:34:30 +00:00
It is common practice in Ansible AWS modules to have a `purge_tags` parameter that defaults to
true.
2017-05-11 06:39:51 +00:00
2017-08-23 14:34:30 +00:00
The `purge_tags` parameter means that existing tags will be deleted if they are not specified by
the Ansible task.
2017-05-11 06:39:51 +00:00
2017-08-23 14:34:30 +00:00
There is a helper function `compare_aws_tags` to ease dealing with tags. It can compare two dicts
and return the tags to set and the tags to delete. See the Helper function section below for more
detail.
2017-05-11 06:39:51 +00:00
2016-04-06 13:37:52 +00:00
### Helper functions
2017-08-23 14:34:30 +00:00
Along with the connection functions in Ansible ec2.py module_utils, there are some other useful
functions detailed below.
2016-04-06 13:37:52 +00:00
#### camel_dict_to_snake_dict
2017-08-23 14:34:30 +00:00
boto3 returns results in a dict. The keys of the dict are in CamelCase format. In keeping with
Ansible format, this function will convert the keys to snake_case.
2016-04-06 13:37:52 +00:00
2018-03-02 19:03:12 +00:00
`camel_dict_to_snake_dict` takes an optional parameter called `ignore_list` which is a list of
keys not to convert (this is usually useful for the `tags` dict, whose child keys should remain with
case preserved)
2018-04-26 12:26:03 +00:00
Another optional parameter is `reversible` . By default, `HTTPEndpoint` is converted to `http_endpoint` ,
2018-03-02 19:03:12 +00:00
which would then be converted by `snake_dict_to_camel_dict` to `HttpEndpoint` .
Passing `reversible=True` converts HTTPEndpoint to `h_t_t_p_endpoint` which converts back to `HTTPEndpoint` .
#### snake_dict_to_camel_dict
`snake_dict_to_camel_dict` converts snake cased keys to camel case. By default, because it was
first introduced for ECS purposes, this converts to dromedaryCase. An optional
parameter called `capitalize_first` , which defaults to `False` , can be used to convert to CamelCase.
2016-04-06 13:37:52 +00:00
#### ansible_dict_to_boto3_filter_list
2017-08-23 14:34:30 +00:00
Converts a an Ansible list of filters to a boto3 friendly list of dicts. This is useful for any
boto3 `_facts` modules.
2016-04-06 13:37:52 +00:00
2017-08-23 15:40:32 +00:00
### boto_exception
Pass an exception returned from boto or boto3, and this function will consistently get the message from the exception.
2018-03-02 19:03:12 +00:00
Deprecated: use `AnsibleAWSModule` 's `fail_json_aws` instead.
2017-08-23 15:40:32 +00:00
2016-04-06 13:37:52 +00:00
#### boto3_tag_list_to_ansible_dict
2017-08-23 14:34:30 +00:00
Converts a boto3 tag list to an Ansible dict. Boto3 returns tags as a list of dicts containing keys
called 'Key' and 'Value' by default. This key names can be overridden when calling the function.
For example, if you have already camel_cased your list of tags you may want to pass lowercase key
names instead i.e. 'key' and 'value'.
2017-05-11 06:39:51 +00:00
2017-08-23 14:34:30 +00:00
This function converts the list in to a single dict where the dict key is the tag key and the dict
value is the tag value.
2016-04-06 13:37:52 +00:00
#### ansible_dict_to_boto3_tag_list
2017-08-23 14:34:30 +00:00
Opposite of above. Converts an Ansible dict to a boto3 tag list of dicts. You can again override
the key names used if 'Key' and 'Value' is not suitable.
2016-04-06 13:37:52 +00:00
2016-05-11 10:33:52 +00:00
#### get_ec2_security_group_ids_from_names
2016-05-04 05:30:30 +00:00
2017-08-23 14:34:30 +00:00
Pass this function a list of security group names or combination of security group names and IDs
and this function will return a list of IDs. You should also pass the VPC ID if known because
security group names are not necessarily unique across VPCs.
2016-12-22 12:22:40 +00:00
2018-03-09 21:19:55 +00:00
#### compare_policies
Pass two dicts of policies to check if there are any meaningful differences and returns true
if there are. This recursively sorts the dicts and makes them hashable before comparison.
This method should be used any time policies are being compared so that a change in order
doesn't result in unnecessary changes.
2017-04-04 18:11:21 +00:00
#### sort_json_policy_dict
2016-12-22 12:22:40 +00:00
2017-08-23 14:34:30 +00:00
Pass any JSON policy dict to this function in order to sort any list contained therein. This is
useful because AWS rarely return lists in the same order that they were submitted so without this
function, comparison of identical policies returns false.
2017-05-11 06:39:51 +00:00
2018-03-09 21:19:55 +00:00
Note if your goal is to check if two policies are the same you're better to use the `compare_policies`
helper which sorts recursively.
#### compare_aws_tags
2017-05-11 06:39:51 +00:00
2017-08-23 14:34:30 +00:00
Pass two dicts of tags and an optional purge parameter and this function will return a dict
containing key pairs you need to modify and a list of tag key names that you need to remove. Purge
is True by default. If purge is False then any existing tags will not be modified.
2017-05-11 06:39:51 +00:00
2017-08-23 14:34:30 +00:00
This function is useful when using boto3 'add_tags' and 'remove_tags' functions. Be sure to use the
other helper function `boto3_tag_list_to_ansible_dict` to get an appropriate tag dict before
calling this function. Since the AWS APIs are not uniform (e.g. EC2 versus Lambda) this will work
without modification for some (Lambda) and others may need modification before using these values
(such as EC2, with requires the tags to unset to be in the form `[{'Key': key1}, {'Key': key2}]` ).
2018-03-02 01:40:01 +00:00
## Integration Tests for AWS Modules
All new AWS modules should include integration tests to ensure that any changes in AWS APIs that
affect the module are detected. At a minimum this should cover the key API calls and check the
documented return values are present in the module result.
For general information on running the integration tests see the [Integration Tests page of the
2018-04-22 07:15:16 +00:00
Module Development Guide](https://docs.ansible.com/ansible/latest/dev_guide/testing_integration.html).
Particularly the [cloud test configuration section ](https://docs.ansible.com/ansible/latest/dev_guide/testing_integration.html#other-configuration-for-cloud-tests )
2018-03-02 01:40:01 +00:00
The integration tests for your module should be added in `test/integration/targets/MODULE_NAME` .
You must also have a aliases file in `test/integration/targets/MODULE_NAME/aliases` . This file serves
two purposes. First indicates it's in an AWS test causing the test framework to make AWS credentials
available during the test run. Second putting the test in a test group causing it to be run in the
continuous integration build.
Tests for new modules should be added to the same group as existing AWS tests. In general just copy
2018-03-27 08:55:25 +00:00
an existing aliases file such as the [aws_s3 tests aliases file ](/test/integration/targets/aws_s3/aliases ).
2018-03-02 01:40:01 +00:00
### AWS Credentials for Integration Tests
The testing framework handles running the test with appropriate AWS credentials, these are made available
to your test in the following variables:
* `aws_region`
* `aws_access_key`
* `aws_secret_key`
* `security_token`
So all invocations of AWS modules in the test should set these parameters. To avoid duplication these
for every call, it's preferrable to use [YAML Anchors ](http://blog.daemonl.com/2016/02/yaml.html ) E.g.
```yaml
- name: set connection information for all tasks
set_fact:
aws_connection_info: & aws_connection_info
aws_access_key: "{{ aws_access_key }}"
aws_secret_key: "{{ aws_secret_key }}"
security_token: "{{ security_token }}"
region: "{{ aws_region }}"
no_log: yes
- name: Do Something
ec2_instance:
... params ...
< < : * aws_connection_info
- name: Do Something Else
ec2_instance:
... params ...
< < : * aws_connection_info
```
### AWS Permissions for Integration Tests
2018-04-22 07:15:16 +00:00
As explained in the [Integration Test guide ](https://docs.ansible.com/ansible/latest/dev_guide/testing_integration.html#iam-policies-for-aws )
2018-03-02 01:40:01 +00:00
there are defined IAM policies in `hacking/aws_config/testing_policies/` that contain the necessary permissions
to run the AWS integration test.
If your module is interacting with a new service or otherwise requires new permissions you must update the
appropriate policy file to grant the permissions needed to run your integration test.
There is no process for automatically granting additional permissions to the roles used by the continuous
integration builds, so the tests will initially fail when you submit a pull request and the
[Ansibullbot ](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md ) will tag it as needing revision.
Once you're certain the failure is only due to the missing permissions, add a comment with the `ready_for_review`
tag and explain that it's due to missing permissions.