Hello, in this post, I want to show another small code example, which I will use a lot in this project.

One problem that Ansible has, in my opinion, is the way it handles inventory, static files with a list of hosts, for me it is not viable, and would be a red flag. In current environments, where we have hundreds of thousands of hosts in as-a-code infrastructures, with pipelines, CI/CD, everything so dynamic, it is unthinkable to have to control files like this. At this point, SaltStack has a huge advantage.

To try to minimize this negative point, here is an example of how to create a dynamic inventory for Ansible in Python. It’s far from ideal, but it already opens the door to many automations and dynamism.

I wanted to make the example as simple as possible, without inventing complications and making it easy to understand.

The most important point is the formatting of the Json that Ansible expects in return, it must have this basic structure that I show below.

Important note, there is no way to pass parameters from your ansible-playbook call to the python script, I’ve seen several examples using arparser, but if you do a careful debug, you’ll realize that it doesn’t work. After some consultations, research and testing, the best way, especially in the case of this project, is to pass values ​​through environment variables.

I say in the case of this project, because Ansibles will be called from Jenkins pipelines where containers are created for each run, so I don’t worry about having as many environment variables as I need, in fact, this is how Jenkins passes most of the variables for the environment.

Back to the Python code, it doesn’t matter how you generate this code, so I made it as simple as possible for better understanding, with the fixed Json, without codes to generate the inventory, nothing. How you will generate the code part of the inventory is up to you, whether by querying Consul, fixed, files, database, terraform, blueprints, dynamic construction, Jenkins, etc, etc, etc. I will probably build the Json using Consul queries and blueprints.

Json example

{
    "_meta": {
        "hostvars": {
            "srv-infrastructure-test-master-01": {
                "ansible_host": "172.21.5.157",
            },
            "srv-infrastructure-jenkins-master-01": {
                "ansible_host": "172.21.5.154",
            },
        }
    },
    "all": {
        "children": [
            "tests_Hosts"
        ]
    },
    "tests_Hosts": {
        "hosts": [
            "srv-infrastructure-test-master-01",
            "srv-infrastructure-jenkins-master-01"
        ],
        "vars": {
            "host_group": self.host_group,
            "host_tech": self.host_tech,
            "host_service": self.host_service
        }
    }
}

The important thing in this Json is to have the “_meta.hostvars” groups with the names of the hosts and ansible_hosts. Host groups must be in “all.children” in list format. And then, the groups with the hosts and optionally the vars.

The Python code example:

#!/usr/bin/env python3

import json
import os

class DynamicInventory(object):
    """
    Example of a class for returning Dynamic Inventory to an Ansible playbook.
    :return: Json with the inventory.
    :rtype: json.
    """
    def __init__(self):
        self.json_inventory = {}
#
        self.host_group = os.environ.get('host_group')
        self.host_tech = os.environ.get('host_tech')
        self.host_service = os.environ.get('host_service')
#
        self.json_inventory = self.LoadInventory()
#
        print(json.dumps(self.json_inventory))
# Empty inventory for testing.
    def EmptyInventory(self):
        """
        Returns an empty Inventory, in case of errors.
        :return: Json with the inventory.
        :rtype: json.
        """
        return {'_meta': {'hostvars': {}}}
#
# Example inventory for testing.
    def LoadInventory(self):
        """
        Example of a method for creating an inventory return Json.
        :return: Json with the inventory.
        :rtype: json.
        """
        return {
                "_meta": {
                    "hostvars": {
                        "srv-infrastructure-test-master-01": {
                            "ansible_host": "172.21.5.157",
                        },
                        "srv-infrastructure-jenkins-master-01": {
                            "ansible_host": "172.21.5.154",
                        },
                    }
                },
                "all": {
                    "children": [
                        "tests_Hosts"
                    ]
                },
                "tests_Hosts": {
                    "hosts": [
                        "srv-infrastructure-test-master-01",
                        "srv-infrastructure-jenkins-master-01"
                    ],
                    "vars": {
                        "host_group": self.host_group,
                        "host_tech": self.host_tech,
                        "host_service": self.host_service
                    }
                }
            }

if __name__ == "__main__":
    try:
        # Get the inventory.
        DynamicInventory()
    except Exception as e:
        print(json.dumps({"_meta": {"hostvars": {}}}))

To validate the inventory, you can run the following command ansible-inventory:

$ ansible-inventory all -i /work/Inventory/global_hosts/inventory_json.py --list

{
    "_meta": {
        "hostvars": {
            "srv-infrastructure-jenkins-master-01": {
                "ansible_host": "172.21.5.154",
                "host_group": "infrastructure",
                "host_service": "devops-db.info",
                "host_tech": "dns"
            },
            "srv-infrastructure-test-master-01": {
                "ansible_host": "172.21.5.157",
                "host_group": "infrastructure",
                "host_service": "devops-db.info",
                "host_tech": "dns"
            }
        }
    },
    "all": {
        "children": [
            "ungrouped",
            "tests_Hosts"
        ]
    },
    "tests_Hosts": {
        "hosts": [
            "srv-infrastructure-test-master-01",
            "srv-infrastructure-jenkins-master-01"
        ]
    }
}

So I’m going to run a test playbook, created just to show the variables that I fill dynamically:

ansible-playbook /work/Inventory/playbooks/dynamic_inventory.yaml -i /work/Inventory/global_hosts/inventory_json.py --vault-id prod@/root/.vault_password.sec

Return:

PLAY [tests_Hosts] ***********************************************************************************************************************************************************************************************************************************************************************************************************

TASK [Gathering Facts] *******************************************************************************************************************************************************************************************************************************************************************************************************
ok: [srv-infrastructure-jenkins-master-01]
ok: [srv-infrastructure-test-master-01]

TASK [dynamic_inventory : output the global playbook 'group' variable] *******************************************************************************************************************************************************************************************************************************************************
ok: [srv-infrastructure-test-master-01] => {
    "host_group": "infrastructure"
}
ok: [srv-infrastructure-jenkins-master-01] => {
    "host_group": "infrastructure"
}

TASK [dynamic_inventory : output the global playbook 'tech' variable] ********************************************************************************************************************************************************************************************************************************************************
ok: [srv-infrastructure-test-master-01] => {
    "host_tech": "dns"
}
ok: [srv-infrastructure-jenkins-master-01] => {
    "host_tech": "dns"
}

TASK [dynamic_inventory : output the global playbook 'service' variable] *****************************************************************************************************************************************************************************************************************************************************
ok: [srv-infrastructure-test-master-01] => {
    "host_service": "devops-db.info"
}
ok: [srv-infrastructure-jenkins-master-01] => {
    "host_service": "devops-db.info"
}

PLAY RECAP *******************************************************************************************************************************************************************************************************************************************************************************************************************
srv-infrastructure-jenkins-master-01 : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
srv-infrastructure-test-master-01 : ok=4    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.