Dynamic Inventory Plugin
A dynamic inventory plugin allows users to point at data sources to compile the inventory of hosts that Ansible uses to target tasks, either via the -i /path/to/file
and/or -i 'host1, host2'
command line parameters or from other configuration sources.
When using Ansible with AWS, inventory file maintenance will be a hectic task as AWS frequently changes IPs, autoscaling instances, and more.
Once your AWS EC2 hosts are spun up, you’ll probably want to talk to them again.
With a cloud setup, it’s best not to maintain a static list of cloud hostnames in text files.
Rather, the best way to handle this is to use the aws_ec2
dynamic inventory plugin.
The aws_ec2
dynamic inventory plugin makes API calls to AWS to get a list of inventory hosts from Amazon Web Services EC2 in the run time.
It gives the EC2 instance details dynamically to manage the AWS infrastructure.
The plugin will also return instances that were created outside of Ansible and allow Ansible to manage them.
To start using the aws_ec2
dynamic inventory plugin with a YAML configuration source, create a file with the accepted filename schema documented for the plugin (a YAML configuration file that ends with aws_ec2.(yml|yaml)
, e.g., demo.aws_ec2.yml
), then add plugin: amazon.aws.aws_ec2
. Use the fully qualified name if the plugin is in a collection.
Authentication
If your Ansible controller is not in AWS, authentication is handled by either specifying your access and secret key as ENV variables or inventory plugin arguments.
For environment variables:
export AWS_ACCESS_KEY_ID='AK123'
export AWS_SECRET_ACCESS_KEY='abc123'
The AWS_SECURITY_TOKEN
environment variable can also be used, but is only supported for backward compatibility.
The AWS_SECURITY_TOKEN
is a replacement for AWS_SESSION_TOKEN
and it is only needed when you are using temporary credentials.
Or you can set aws_access_key
, aws_secret_key
, and security_token
inside the inventory configuration file.
# demo.aws_ec2.yml
plugin: amazon.aws.aws_ec2
# The access key for your AWS account.
aws_access_key: <YOUR-AWS-ACCESS-KEY-HERE>
# The secret access key for your AWS account.
aws_secret_key: <YOUR-AWS-SECRET-KEY-HERE>
If you use different credentials for different tools or applications, you can use profiles.
The profile
argument is mutually exclusive with the aws_access_key
, aws_secret_key
and security_token
options.
When no credentials are explicitly provided then the AWS SDK (boto3) which Ansible uses will fall back to its configuration files (typically ~/.aws/credentials
).
The shared credentials file has a default location of ~/.aws/credentials
.
You can change the location of the shared credentials file by setting the AWS_SHARED_CREDENTIALS_FILE
environment variable.
# demo.aws_ec2.yml
plugin: amazon.aws.aws_ec2
# Attach the default AWS profile
aws_profile: default
# You could use Jinja2 to attach the AWS profile from the environment variable.
aws_profile: "{{ lookup('env', 'AWS_PROFILE') | default('dev-profile', true) }}"
You can also set your AWS profile as an ENV variable:
export AWS_PROFILE='test-profile'
If your Ansible controller is running on an EC2 instance with an assigned IAM Role, the credential may be omitted. See the documentation for the controller for more details.
You can also use the ARN of the IAM role to assume to perform the inventory lookup.
This can be useful for connecting across different accounts, or to limit user access.
To do so, you should specify the iam_role_arn
.
You should still provide AWS credentials with enough privilege to perform the AssumeRole action.
# demo.aws_ec2.yml
plugin: amazon.aws.aws_ec2
iam_role_arn: arn:aws:iam::1234567890:role/assumed-ansible
Minimal Example
Fetch all hosts in us-east-1, the hostname is the public DNS if it exists, otherwise the private IP address.
# demo.aws_ec2.yml
plugin: amazon.aws.aws_ec2
# This sets the region. If empty (the default) default this will include all regions, except possibly
# restricted ones like us-gov-west-1 and cn-north-1.
regions:
- us-east-1
After providing any required options, you can view the populated inventory with ansible-inventory -i demo.aws_ec2.yml --graph
:
@all:
|--@aws_ec2:
| |--ip-10-210-0-189.ec2.internal
| |--ip-10-210-0-195.ec2.internal
|--@ungrouped:
Allowed Options
Some of the aws_ec2
dynamic inventory plugin options are explained in detail below. For a full list see the plugin documentation.
hostnames
hostnames
option provides different settings to choose how the hostname will be displayed.
Some examples are shown below:
hostnames:
# This option allows displaying the public ip addresses.
- ip-address
# This option allows displaying the private ip addresses using `tag:Name` as a prefix.
# `name` can be one of the options specified in http://docs.aws.amazon.com/cli/latest/reference/ec2/describe-instances.html#options.
- name: 'private-ip-address'
separator: '_'
prefix: 'tag:Name'
# Using literal values for hostname
# # Hostname will be aws-test_literal
- name: 'test_literal'
separator: '-'
prefix: 'aws'
# To use tags as hostnames use the syntax `tag:Name=Value` to use the hostname `Name_Value`, or
# `tag:Name` to use the value of the Name tag. If value provided does not exist in the above options,
# it will be used as a literal string.
- name: 'tag:Tag1=Test1,Tag2=Test2'
# Use dns-name attribute as hostname
- dns-name
# You can also specify a list in order of precedence for hostname variables.
- ip-address
- dns-name
- tag:Name
- private-ip-address
By default, the inventory will only return the first match one of the hostnames
entries.
You may want to get all the potential matches in your inventory, this also implies you will get
duplicated entries. To switch to this behavior, set the allow_duplicated_hosts
configuration key to True
.
keyed_groups
You can create dynamic groups using host variables with the keyed_groups
option. keyed_groups
comes in a prefix and a key format.
The prefix will be the name of the host group that is to be concatenated with the key.
Some examples are shown below:
keyed_groups:
# This creates host groups based on architecture.
- prefix: arch
key: architecture
# This creates host groups based on `x86_64` architecture.
- prefix: arch
key: architecture
value:
'x86_64'
# This creates host groups based on availability zone.
- prefix: az
key: placement.availability_zone
# If the EC2 tag Name had the value `redhat` the tag variable would be: `tag_Name_redhat`.
# Similarly, if a tag existed for an AWS EC2 instance as `Applications` with the value of `nodejs` the
# variable would be: `tag_Applications_nodejs`.
- prefix: tag
key: tags
# This creates host groups using instance_type, e.g., `instance_type_z3_tiny`.
- prefix: instance_type
key: instance_type
# This creates host groups using security_groups id, e.g., `security_groups_sg_abcd1234` group for each security group.
- key: 'security_groups|json_query("[].group_id")'
prefix: 'security_groups'
# This creates a host group for each value of the Application tag.
- key: tags.Application
separator: ''
# This creates a host group per region e.g., `aws_region_us_east_2`.
- key: placement.region
prefix: aws_region
# This creates host groups based on the value of a custom tag `Role` and adds them to a metagroup called `project`.
- key: tags['Role']
prefix: foo
parent_group: "project"
# This creates a common parent group for all EC2 availability zones.
- key: placement.availability_zone
parent_group: all_ec2_zones
# This creates a group per distro (distro_CentOS, distro_Debian) and assigns the hosts that have matching values to it,
# using the default separator "_".
- prefix: distro
key: ansible_distribution
groups
It is also possible to create groups using the groups
option.
Some examples are shown below:
groups:
# This created two groups - `Production` and `PreProduction` based on tags
# These conditionals are expressed using Jinja2 syntax.
redhat: "'Production' in tags.Environment"
ubuntu: "'PreProduction' in tags.Environment"
# This created a libvpc group based on specific condition on `vpc_id`.
libvpc: vpc_id == 'vpc-####'
compose
compose
creates and modifies host variables from Jinja2 expressions.
compose:
# This sets the ansible_host variable to connect with the private IP address without changing the hostname.
ansible_host: private_ip_address
# This sets location_vars variable as a dictionary with location as a key.
location_vars:
location: "east_coast"
server_type: "ansible_hostname | regex_replace ('(.{6})(.{2}).*', '\\2')"
# This sets location variable.
location: "'east_coast'"
# This lets you connect over SSM to the instance id.
ansible_host: instance_id
ansible_connection: 'community.aws.aws_ssm'
# This defines combinations of host servers, IP addresses, and related SSH private keys.
ansible_host: private_ip_address
ansible_user: centos
ansible_ssh_private_key_file: /path/to/private_key_file
# This sets the ec2_security_group_ids variable.
ec2_security_group_ids: security_groups | map(attribute='group_id') | list | join(',')
# Host variables that are strings need to be wrapped with two sets of quotes.
# See https://docs.ansible.com/ansible/latest/plugins/inventory.html#using-inventory-plugins for details.
ansible_connection: '"community.aws.aws_ssm"'
ansible_user: '"ssm-user"'
include_filters
and exclude_filters
include_filters
and exclude_filters
options give you the ability to compose the inventory with several queries (see available filters).
include_filters:
# This includes everything in the inventory that has the following tags.
- tag:Project:
- 'planets'
- tag:Environment:
- 'demo'
# This excludes everything from the inventory that has the following tag:Name.
exclude_filters:
- tag:Name:
- '{{ resource_prefix }}_3'
filters
filters
are used to filter out AWS EC2 instances based on conditions (see available filters).
filters:
# This selects only running instances with tag `Environment` tag set to `dev`.
tag:Environment: dev
instance-state-name : running
# This selects only instances with tag `Environment` tag set to `dev` and `qa` and specific security group id.
tag:Environment:
- dev
- qa
instance.group-id: sg-xxxxxxxx
# This selects only instances with tag `Name` fulfilling specific conditions.
- tag:Name:
- dev-*
- share-resource
- hotfix
use_contrib_script_compatible_ec2_tag_keys
and use_contrib_script_compatible_sanitization
use_contrib_script_compatible_ec2_tag_keys
exposes the host tags with ec2_tag_TAGNAME keys like the old ec2.py inventory script when it’s True.
By default the aws_ec2
plugin is using a general group name sanitization to create safe and usable group names for use in Ansible.
use_contrib_script_compatible_ec2_tag_keys
allows you to override that, in efforts to allow migration from the old inventory script and matches the sanitization of groups when the script’s replace_dash_in_groups option is set to False.
To replicate behavior of replace_dash_in_groups = True with constructed groups, you will need to replace hyphens with underscores via the regex_replace filter for those entries.
For this to work you should also turn off the TRANSFORM_INVALID_GROUP_CHARS setting, otherwise the core engine will just use the standard sanitization on top.
This is not the default as such names break certain functionality as not all characters are valid Python identifiers which group names end up being used as.
The use of this feature is discouraged and we advise to migrate to the new tags structure.
# demo.aws_ec2.yml
plugin: amazon.aws.aws_ec2
regions:
- us-east-1
filters:
tag:Name:
- 'instance-*'
hostnames:
- tag:Name
use_contrib_script_compatible_sanitization: True
use_contrib_script_compatible_ec2_tag_keys: True
After providing any required options, you can view the populated inventory with ansible-inventory -i demo.aws_ec2.yml --list
:
{
"_meta": {
"hostvars": {
"instance-01": {
"aws_ami_launch_index_ec2": 0,
"aws_architecture_ec2": "x86_64",
...
"ebs_optimized": false,
"ec2_tag_Environment": "dev",
"ec2_tag_Name": "instance-01",
"ec2_tag_Tag1": "Test1",
"ec2_tag_Tag2": "Test2",
"ena_support": true,
"enclave_options": {
"enabled": false
},
...
},
"instance-02": {
...
"ebs_optimized": false,
"ec2_tag_Environment": "dev",
"ec2_tag_Name": "instance-02",
"ec2_tag_Tag1": "Test3",
"ec2_tag_Tag2": "Test4",
"ena_support": true,
"enclave_options": {
"enabled": false
},
...
}
}
},
all": {
"children": [
"aws_ec2",
"ungrouped"
]
},
"aws_ec2": {
"hosts": [
"instance-01",
"instance-02"
]
}
}
hostvars_prefix
and hostvars_suffix
hostvars_prefix
and hostvars_sufix
allow to set up a prefix and suffix for host variables.
# demo.aws_ec2.yml
plugin: amazon.aws.aws_ec2
regions:
- us-east-1
filters:
tag:Name:
- 'instance-*'
hostvars_prefix: 'aws_'
hostvars_suffix: '_ec2'
hostnames:
- tag:Name
Now the output of ansible-inventory -i demo.aws_ec2.yml --list
:
{
"_meta": {
"hostvars": {
"instance-01": {
"aws_ami_launch_index_ec2": 0,
"aws_architecture_ec2": "x86_64",
"aws_block_device_mappings_ec2": [
{
"device_name": "/dev/sda1",
"ebs": {
"attach_time": "2022-06-27T09:04:57+00:00",
"delete_on_termination": true,
"status": "attached",
"volume_id": "vol-06e065bca44e6eae5"
}
}
],
"aws_capacity_reservation_specification_ec2": {
"capacity_reservation_preference": "open"
}
...,
},
"instance-02": {
...,
}
}
},
all": {
"children": [
"aws_ec2",
"ungrouped"
]
},
"aws_ec2": {
"hosts": [
"instance-01",
"instance-02"
]
}
}
strict
and strict_permissions
strict: False
will skip instead of producing an error if there are missing facts.
strict_permissions: False
will ignore 403 errors rather than failing.
cache
aws_ec2
inventory plugin support caching can use the general settings for the fact cache defined in the ansible.cfg
file’s [defaults]
section or define inventory-specific settings in the [inventory]
section.
You can can define plugin-specific cache settings in the config file:
# demo.aws_ec2.yml
plugin: aws_ec2
# This enables cache.
cache: yes
# Plugin to be used.
cache_plugin: jsonfile
cache_timeout: 7200
# Location where files are stored in the cache.
cache_connection: /tmp/aws_inventory
cache_prefix: aws_ec2
Here is an example of setting inventory caching with some fact caching defaults for the cache plugin used and the timeout in an ansible.cfg
file:
[defaults]
fact_caching = ansible.builtin.jsonfile
fact_caching_connection = /tmp/ansible_facts
cache_timeout = 3600
[inventory]
cache = yes
cache_connection = /tmp/ansible_inventory
Complex Example
Here is an aws_ec2
complex example utilizing some of the previously listed options:
# demo.aws_ec2.yml
plugin: amazon.aws.aws_ec2
regions:
- us-east-1
- us-east-2
keyed_groups:
# add hosts to tag_Name_value groups for each aws_ec2 host's tags.Name variable.
- key: tags.Name
prefix: tag_Name_
separator: ""
groups:
# add hosts to the group dev if any of the dictionary's keys or values is the word 'dev'.
development: "'dev' in (tags|list)"
filters:
tag:Name:
- 'instance-01'
- 'instance-03'
include_filters:
- tag:Name:
- 'instance-02'
- 'instance-04'
exclude_filters:
- tag:Name:
- 'instance-03'
- 'instance-04'
hostnames:
# You can also specify a list in order of precedence for hostname variables.
- ip-address
- dns-name
- tag:Name
- private-ip-address
compose:
# This sets the `ansible_host` variable to connect with the private IP address without changing the hostname.
ansible_host: private_ip_address
If a host does not have the variables in the configuration above (i.e. tags.Name
, tags
, private_ip_address
), the host will not be added to groups other than those that the inventory plugin creates and the ansible_host
host variable will not be modified.
Now the output of ansible-inventory -i demo.aws_ec2.yml --graph
:
@all:
|--@aws_ec2:
| |--instance-01
| |--instance-02
|--@tag_Name_instance_01:
| |--instance-01
|--@tag_Name_instance_02:
| |--instance-02
|--@ungrouped:
Using Dynamic Inventory Inside Playbook
If you want to use dynamic inventory inside the playbook, you just need to mention the group name in the hosts variable as shown below.
---
- name: Ansible Test Playbook
gather_facts: false
hosts: tag_Name_instance_02
tasks:
- name: Run Shell Command
command: echo "Hello World"