10 best Ansible modules for infrastructure as code

10 (plus a bonus) Ansible automation modules that anyone—from a beginner to a power user—can leverage to transform their network infrastructure to code.

In 2017, I emailed several colleagues about a new discovery. I had stumbled upon Ansible, a new open-source network automation tool, and thought it could help us solve some particularly complex and large-scale problems.

Was I ever right! Since then, I’ve gone on to write hundreds of playbooks to automate everything from enterprise networks to compute and storage, and even entire clouds.

Below are my top 10 (plus a bonus) recommended Ansible modules that anyone—from a beginner to a power user—can leverage to transform their infrastructure to code.

10. Vars prompt

9. Shell

8. Set fact

7. Register

6. Copy

5. Command and configuration

4. Vault

3. Facts and information

2. Template

1. URI

Bonus: Debug

But first, what is infrastructure as code and why does it matter?

Infrastructure as code is a modern approach to managing networks, compute, storage, load-balancing, firewalls, and appliances (like BlueCat Address Manager). Unlike traditional infrastructure management, it relies on a computer programming approach instead of a command-line or graphical user interface.

This matters because all modern—or, at the very least, next-generation—infrastructure comes equipped with a REST API. The ability to program infrastructure using a REST API allows for the maturity of the software development lifecycle. This offers numerous advantages, including:

  • version and source control;
  • a central source of truth; and
  • the ability to build, release, and monitor using Docker container images in a continuously integrated (CI) and continuously delivered (CD) pipeline.

No. 10: Vars prompt

One of the first steps in any Ansible playbook is to establish secure connectivity to a target host. Regardless of the connection type—be it SSH, HTTPS, or WinRM—the playbook needs to authenticate with a set of credentials. The first mistake engineers tend to make—often to expedite development or out of pure ignorance—is to hard-code their credentials into their group_vars or directly in their playbook.

# Network provider
ansible_connection: network_cli
ansible_network_os: ios
ansible_user: admin
ansible_ssh_pass: MyPa$$W0rd123

The problem with this approach is that infrastructure as code should be stored in a version or source control system like Git. However, this means you are storing highly secure credentials in plain text for all to see inside the Git repository. Not good.

Instead, we can use the vars_prompt module to prompt for input from the user.

This has several advantages, including not hard-coding your credentials. It also allows for collaboration, as every user can provide their own credentials at run-time.

- hosts: IOS
    - name: IOS_User_Prompt
      prompt: “Enter Cisco IOS Username”
      private: no

    - name: IOS_Password_Prompt
      prompt: “Enter IOS Password”
      private: yes

# Register Username and Password
    - set_fact:
        ios_user: “{{ IOS_User_Prompt }}”
        ios_password: “{{ IOS_Password_Prompt }}”

# Network provider:
ansible_connection: network_cli
ansible_network_os: ios
ansible_user: “{{ hostvars[inventory_hostname][‘ios_user’] }}”
ansible_ssh_pass: “{{ hostvars[inventory_hostname][‘ios_password’] }}”

For further reading, Ansible’s interactive prompts guide is helpful.

No. 9: Shell

The ability to interact with the Linux host the playbook is running on opens up operating system-level commands and control from within a playbook. Any Linux command you can think of can be executed from inside your playbook. I like to use this ability to automate the Git actions at the end of a playbook.

# Git Add / Commit / Push
- hosts: localhost
    - set_fact:
        git_message: “Automated Release Update”
    - name: Git - Add
      shell: “git add ../*”
      register: gitadd
     - name: Git - Commit
      shell: “git commit -am “{{ git_message }}”
      register: git commit
    - name: Git - Push
      shell: “git push”
      register: git push

For further reading, Ansible’s guide to execute shell commands on targets is helpful.

No. 8: Set fact

A key component of infrastructure as code is the ability to work with variables. The ability to set facts, or create your own variables, is extremely powerful and a key differentiator from the legacy manual methodologies of days past. In the previous example, the git_message variable was defined, or set as a fact, with a hard-coded string (“Automated Release Update”).

It’s important to note that you are not limited to just hard-coded values. You can set facts from other facts or data retrieved from devices. In the following example, I am parsing JSON and creating a variable called device_entity with the results from the JSON_Query.

# Set Device Fact
  - name: Set Device Fact
      device_entity: “{{ device_id.json.queryResponse.entityID[0][‘$’] }}”

For further reading, Ansible guides on set host facts from a task, using variables, and filters and JSON_query are helpful.

No. 7: Register

Continuing with the theme of variables, the ability to register data into a variable is slightly different than setting a one-time fact. For example, when you iterate over a loop, if you use set_fact, only the data from the last iteration of the loop would end up inside your variable. This can often lead to confusion and false positive development issues. Namely,  where is the rest of the data from all the other loops?

Register has you covered. It allows you to, well, register the results from each loop into a dynamic flexible variable. For example, you can get the networks from the BlueCat Address Manager (BAM) REST API registered into a variable.

# GET IP Network #
# Replace BAM with your BAM IP or Hostname #
    - name: GET Network Details
        url: http://BAM/Services/REST/v1/searchByObjectTypes?count=100&start=0&types=IP4Network&keyword=*
        method: GET
          Authorization: "{{ token }}"
          Cache-Control: no-cache
          Accept-Encoding: gzip, deflate, br
          Connection: keep-alive
          Accept: application/json 
        return_content: yes
        validate_certs: no
        force_basic_auth: yes
        follow_redirects: yes
      register: network

To create a variable inside another task from received data, you also need to register it. Here is a simple example using the ios_command module where I want to register the response into a variable.

# Run show mac address-table and register results #
    - name: show mac address-table 
          - show mac address-table
      register: mac_table_raw

The key difference here is that set_fact is a stand-alone task all on its own that creates a variable while register creates a variable with the results of another task. Pay close attention to this if you are new to Ansible.

For further reading, Ansible’s guide on registering variables, as well as my BlueCat Address Manager Network Facts playbook on GitHub, are helpful.

No. 6: Copy

The ability to orchestrate and automate file manipulation is a powerful capability. If you read the documentation for this module, it may seem like copying a source file to a local or remote destination is all this module can do. So, how did it make it so high up on this list?

Sure, you can copy files, as seen in this example:

    - name: Copy file with owner and permissions
        src: /srv/myfiles/foo.conf
        dest: /etc/foo.conf
        owner: foo
        group: foo
        mode: '0644'

But what I like to do with the copy module is create files from the variables the set_fact and register modules have created.

A good example of this, and the next step in the BlueCat Address Manager Network Facts playbook, is to take the registered variable and create a nice (pretty) JSON file from the data. I am using the Ansible filter | to_nice_json to achieve this.

# Create JSON file with Ansible Facts #
    - name: Create raw JSON file
        content: | 
          {{ network | to_nice_json }}
        dest: ../documentation/BAM/Network_Facts_Nice.json

Which is now in the Git repository and looks like this:

"json": [
           "id": 100899,
           "name": "sample",
           "properties": "CIDR=|allowDuplicateHost=disable|inheritAllowDuplicateHost=true|pingBeforeAssign=disable|inheritPingBeforeAssign=true|gateway=|inheritDefaultDomains=true|defaultView=100893|inheritDefaultView=true|inheritDNSRestrictions=true|",
           "type": "IP4Network"

And since I am partial to reading YAML, I also create another file using the | to_nice_yaml filter.

# Create YAML file with Ansible Facts #
    - name: Create raw YAML
        content: | 
          {{ network | to_nice_yaml }}
        dest: ../documentation/BAM/Network_Facts.yml

Which, respectively, looks like this:

-   id: 100899
    name: sample
    properties: CIDR=|allowDuplicateHost=disable|inheritAllowDuplicateHost=true|pingBeforeAssign=disable|inheritPingBeforeAssign=true|gateway=|inheritDefaultDomains=true|defaultView=100893|inheritDefaultView=true|inheritDNSRestrictions=true|
    type: IP4Network
-   id: 100907
    name: sample02
    properties: CIDR=|allowDuplicateHost=disable|inheritAllowDuplicateHost=true|pingBeforeAssign=disable|inheritPingBeforeAssign=true|gateway=|inheritDefaultDomains=true|defaultView=100893|inheritDefaultView=true|inheritDNSRestrictions=true|
    type: IP4Network

For further reading, the Ansible guides on copy files to remote locations and playbook filters are helpful.

No. 5: Command and configuration

The most sought-after network automation capability is, obviously, making configuration changes and running orchestrated commands at scale. Almost every IT platform available now has either Ansible or vendor-provided command and configuration modules to programmatically alter configurations.

Command modules—for example on a Cisco Catalyst platform running Internetwork Operating System (OS)—have ios_command modules that allow you to execute privileged exec commands at scale. A good example of this, and a non-disruptive yet highly valuable playbook, could be to perform a copy running-config startup-config command writing the memory. This is usually added to the end of any playbooks that have altered the configuration of the device and when you need to save the changes to memory.

# Copy Run Start
    - name: Write Mem
          - “copy run start\n\n”

Configuration modules go a step further and enter the configuration terminal level (conf t) on the platform and issue configuration commands. For example, if we wanted to configure an IP helper-address on a switch virtual interface (SVI):

# Configure IP Helper Address on Interface VLAN 10
    - name: Update SVI IP Helpers
          - ip helper-address
        parents: interface vlan 10

The examples above are simple but that is kind of the point. You can run any command or configuration you want, at scale, in any order you want, on basically any platform available.

The BlueCat Ansible module extends Ansible to allow for command and configuration within the BlueCat platform.

For further reading, Ansible’s guides to run commands on remote devices running Cisco IOS and manage Cisco IOS configuration sections are helpful.

No. 4: Vault

The ability to prompt for inputs such as credentials is very useful. However, it does come with limitations and will only get you so far. After all, this is supposed to be automation, right? But prompts require human interaction. If you are ever going to evolve and containerize your simple Ansible playbooks into full-fledged CI/CD in a fully automated pipeline, you will need to re-factor any vars_prompt credential handling.

Fortunately, there is a way that allows you to securely hard code your credential strings inside what is known as an Ansible vault. It is very versatile in what it can encrypt. My particular use case is to encrypt my credentials so I can store them safely inside my Git repository.

ansible-vault encrypt_string 'string' --name 'variable_name' 

After running this command, you will be prompted for a secret. You can either provide it at playbook runtime to decrypt the strings or—and this is the key to full automation—you can store the key inside a plain text file available to the playbook. This allows for hands-free automation and containerization in which you can use Docker mount to mount the decryption file to unlock the credentials.

Ultimately, your strings will look like this:

ansible_ssh_pass: !vault |
                  {{ a huge string of numbers here }}

For further reading, Ansible’s guides on Ansible vault and encrypting content with Ansible vault are helpful.

No. 3: Facts and information

The modules that made my top three may come as a bit of a surprise to you. The ability to gather facts and information—you may be wondering how this seemingly simple capability is higher on the list than configuration modules.

But think about how often the network is in a steady-state that you want to document, monitor, validate, and understand versus how often you are affecting change on the network.

Also, I believe that digital information is the new oil and worth a lot. This is particularly true for important information from the network such as the state of DNS. Enterprises can actually profit from these resulting digital insights as well, either with operational excellence, commoditizing the data, or adjusting business flows.

These modules are also safe and the best entry point for enterprises and individuals to start their infrastructure-as-code automation journey. Depending on the platform, these modules go by a variety of different names. But they all achieve the same end: Collecting stateful facts and information and returning the data as structured JSON.

A note on platforms

Windows and Linux platforms use the setup module while VMware has a variety of _info modules (guest_info, host_info, etc.). Cisco has devoted ios_facts, nxos_facts, and even asa_facts modules. In the cloud, Azure and AWS have dozens of _info modules.

BlueCat has implemented a collection of RESTful get_ modules that achieve the same goals. For example, get_configurations.yml returns an Address Manager entity for each configuration in Address Manager. The BlueCat Ansible module includes a series of standalone playbooks.

Using these simple modules, fully automated, fully scaled, real-time, network-state documentation can be stored in a real enterprise-grade source of truth.

For further reading, Ansible’s guides on gather facts about remote hosts (Windows and Linux), VMware, collect facts from remote devices running Cisco IOS, get facts about NX-OS switches, collect facts from remote devices running Cisco Adaptive Security Appliance (ASA), Microsoft Azure, and Amazon Web Services, as well as my blog post about infrastructure as code, are helpful.

No. 2: Template

The most difficult decision I had to make was if the template module was my favorite module or not. It was close. But this extremely powerful module, which opens Ansible up to a Python-powered templating language, Jinja2, ended up with the silver medal.

Jinja2 lets you create templates—just like a Microsoft PowerPoint or Word template or any other cookie-cutter template—that, in turn, create other file types. Jinja2 lets you apply logic, such as If, Else, End If, and even For Loops. And it allows you to plug in variable-data to plug dynamic data into your files.

I’ve used the template module in both directions—both for template data I want to send to a device, such as a configuration, as well as for template data I’ve received from a device (typically the JSON).

For example, first I can generate a full IOS configuration for my network core using a Jinja2 template:

# Generate Core Config
    - name: Generate Core Config
        src: CampusCoreCFG.j2
        dest: ../../intended_config/CAMPUS/CORE/{{ inventory_hostname }}.cfg

In which, for example, the VLAN configuration stanzas look like this in the CampusCoreCFG.j2 file:

{% if host_vlans is defined %}
{%     for host_vlan in host_vlans %}
vlan {{ host_vlan }}
  Name {{ host_vlans[host_vlan].name }}
{%     endfor %}
{% endif %}

Which look like this in the Core.cfg file generated by the template:

vlan 2
  name BLUE_VLAN

vlan 3
  name RED_VLAN

vlan 6

Now, using the earlier ios_config module, we can simply push the templated intended configuration file to the device:

# Push Templated Config to Core
    - name: Push Config to Core
        src: ../../intended_config/CAMPUS/CORE/{{ inventory_hostname }}.cfg

Conversely, with received facts, I also pass the received data through a Jinja2 template to create both a comma-separated value (CSV) spreadsheet file and a markdown file with the JSON data. Yes, using the same template!

# Gather Ansible Facts about Core
    - name: Gather Ansible Facts about Core
          - all

# Create CSV and Markdown file with results #
    - name: Create CSV and Markdown
        src: ../../../roles/campus_core/templates/CiscoCoreFacts.j2          
        dest: ../../../FACTS/CAMPUS/CORE/{{ inventory_hostname }}/IOS_facts.{{ item }}
        - csv
        - md

As you can see, the template uses the Ansible loop iteration and generates either a .csv or .md file depending on the value. Some basic logic and valid output file type formatting (be careful with your markdown, as it’s very strict) result in two files: the CSV and the markdown.

Templating is fast—much faster than using the lineinfile module. It also allows you to keep the logical complexity inside the templates in Pythonic syntax, which is easier to write and maintain than inside the Ansible playbook as YAML.

For further reading, Ansible’s guides on templating (Jinja2) and template a file out to a target host, as well as my blog post about moving to Jinja2 for automated documentation, are helpful.

No. 1: URI

My top Ansible module is the URI module. I chose this module because I firmly believe the future of IT and the backbone of infrastructure as code is the REST API. Any modern appliance, including BlueCat Address Manager, as well as all next-generation network infrastructure and the Cloud, is driven by the RESTful API.

Typically, I will get any given API CRUD (Create, Read, Update, Delete) activity in Postman first to get it working. Once I have my API authentication, headers, and body working in Postman, I port over the code into the Ansible URI module to take that single request and automatically scale it up.

We did a demonstration of this approach, live, using the BlueCat Address Manager REST API:

Authentication is handled with a preliminary REST API call to get a valid token:

# Get RAW Token #
# Replace "BAM" with your BAM IP or Hostname #
    - name: Get Token
        url: http://BAM/Services/REST/v1/login?username=*7B*7B__;JSU!!LGx5ln-YXE48mET8!L5I4_u6rikbdtDBGeHhHAb3_nMZlH3ihGuUavBe9Ay2b7ikHSn5NXyjjL52yTlmPwD9alQ$  username }}&password={{ password }}
          Accept-Encoding: gzip, deflate, br
          Accept: application/json
          Content-Type: application/x-www-form-urlencoded
          User-Agent: Jakarta Commons-HttpClient/3.1
          host: cloudsso.cisco.com
          Connection: keep-alive
          Content-Length: 0
        method: GET
        return_content: yes
        validate_certs: no
        force_basic_auth: yes
        follow_redirects: yes
        body_format: json
      register: rawtoken

Once we have a token, we can then pass it along to the BlueCat Address Manager APIs. In this example, I use the searchByObjectType API:

# GET IP Network #
# Replace BAM with your BAM IP or Hostname #
    - name: GET Network Details
        url: http://BAM/Services/REST/v1/searchByObjectTypes?count=100&start=0&types=IP4Network&keyword=*__;Kg!!LGx5ln-YXE48mET8!L5I4_u6rikbdtDBGeHhHAb3_nMZlH3ihGuUavBe9Ay2b7ikHSn5NXyjjL52yTlmMiflD1A$ 
        method: GET
          Authorization: "{{ token }}"
          Cache-Control: no-cache
          Accept-Encoding: gzip, deflate, br
          Connection: keep-alive
          Accept: application/json 
        return_content: yes
        validate_certs: no
        force_basic_auth: yes
        follow_redirects: yes
      register: network

You can also learn how to use Ansible and Postman or Gateway to get network information from BlueCat Address Manager.

So far, I have used this module to automate:

  • BlueCat Address Manager
  • Cisco.com
    • Serial Number 2 Information API
    • Recommended Software Release
    • Product Security Incident Response Team (PSIRT)
  • Cisco DCNM
  • Cisco Catalyst 3850 / 9300
  • Cisco NXOS
  • Cisco Umbrella
  • Cisco Security Manager
  • F5

Anything with a RESTful API can be automated!

For further reading, Ansible’s guide on interacts with webservices, RedHat’s article on using Ansible to interact with web endpoints, are helpful.

Bonus: Debug

Debug allows you to interact with your terminal during playbook execution and is extremely handy for troubleshooting or development. You can print messages to yourself as a task in your playbook by using debug to send messages to yourself (“Your playbook got here!”). I like to print variables to the screen to check their contents.

I can add this debug as a task, for example, to print to the screen the registered variable network from the URI module calls.

# GET IP Network #
# Replace BAM with your BAM IP or Hostname #
     - name: GET Network Details
         url: http://BAM/Services/REST/v1/searchByObjectTypes?count=100&start=0&types=IP4Network&keyword=*__;Kg!!LGx5ln-YXE48mET8!L5I4_u6rikbdtDBGeHhHAb3_nMZlH3ihGuUavBe9Ay2b7ikHSn5NXyjjL52yTlmMiflD1A$ 
         method: GET
           Authorization: "{{ token }}"
           Cache-Control: no-cache
           Accept-Encoding: gzip, deflate, br
           Connection: keep-alive
           Accept: application/json 
         return_content: yes
         validate_certs: no
         force_basic_auth: yes
         follow_redirects: yes
       register: network

    - debug:
      msg=”{{ network }}”

(You could also filter this | to_nice_json.)

For further reading, Ansible’s guide on print statements during execution is helpful.

Building an automated solution with Ansible really is as simple as picking and choosing the right modules and then organizing and orchestrating the tasks you want these modules to perform. They are all very easy to use and well-documented. There are many real-world examples available on GitHub and other places, my blog included.

Now that you have these 11 modules, I hope that you have a sense of the type of network automation and orchestration available to you. I wish you the best of success with your journey into infrastructure as code. I also hope you find these modules useful, and that if you find more good stuff, that you share it with the rest of our networking community in Network VIP on Slack.

Heading into the cloud?

See how your network can thrive in the complexity of the cloud.

Find answers to all your cloud-related questions.

Access cloud resources

Read more

Five network pros’ manual error horror stories

Members of BlueCat’s Network VIP community detail the errors they committed, the resulting fallout, and what important lessons they learned.

Read more
Cloud Webinar Series: Part 3

Manage overlapping cloud networks like a boss.

Read more
NSA and CISA: Protective DNS key to network defense

U.S. cyber agencies now point to protective DNS as a defense strategy, confirming what BlueCat already knew: DNS is critical to detecting network threats.

Read more
BlueCat Integrity 9.3: Deliver DNS like a boss

With the BlueCat Integrity 9.3 release, network admins can get more audit data, manage complexity, and ramp up automation, without compromising performance.

Read more

Products and Services

From Core Network Services to multicloud management, BlueCat has everything you need to build the network you need.

Learn more