# Ansible Inventories

Let's fist create a file with the name `ansible.cfg`,

```
[defaults]
inventory = hosts
```

Next a file with the name `hosts.ini`,

```
[all]
centos1
```

In here, the `ansible.cfg` file is refering to an invenstory file with the name "hosts" and the `hosts.ini` file consist a host with the name "centos1". (In here it is assumed that a host with the name centos1 is reachable at this point)

Now first, I will remove the known hosts file from the ssh directory.

```
$ rm -rf /home/ansible/.ssh/known_hosts
```

Next when I try to ping to my host, it should asks me to verify the fingerprint, and I'm not going to accept it.

```
$ ansible all -m ping
The authenticity of host 'centos1 (172.18.0.8)' can't be established.
ECDSA key fingerprint is SHA256:gdRFM1dy+ntzCjU1mJi5oBS1k5enVlS/bPz6Wms59Ck.
Are you sure you want to continue connecting (yes/no/[fingerprint])? ^C [ERROR]: User interrupted execution
```

To get around with this, I'm going to ping the hosts again with a varible set up.

```
$ ANSIBLE_HOST_KEY_CHECKING=False ansible all -m ping
centos1 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/libexec/platform-python"
    },
    "changed": false,
    "ping": "pong"
}
```

The variable `ANSIBLE_HOST_KEY_CHECKING` instructs ansible to ignore to fingerprint verification when it is set to `False`. But sometimes it could be repetitive to specify this variable when executing commands. This can also be configured in the `ansible.cfg` file.

```
[defaults]
inventory = hosts
host_key_checking = False
```

To make sure host's are not there, I'm going to remove the known hosts file,

```
$ rm -rf /home/ansible/.ssh/known_hosts
```

Then I'm going to ping our hosts again,

```
$ ansible all -m ping
centos1 | SUCCESS => {
    "ansible_facts": {
        "discovered_interpreter_python": "/usr/libexec/platform-python"
    },
    "changed": false,
    "ping": "pong"
}
```

Now let me slightly modify our invenstory file `hosts.ini` like below,

```
[centos]
centos1
centos2
centos3

[ubuntu]
ubuntu1
ubuntu2
ubuntu3
```

Though we have grouped these under their os type, we can still use the `all` key word which will take all hosts into consideration.

```
$ ansible all -m ping -o
ubuntu1 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"}
ubuntu2 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"}
centos3 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"},"changed": false,"ping": "pong"}
centos1 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"},"changed": false,"ping": "pong"}
centos2 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"},"changed": false,"ping": "pong"}
ubuntu3 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"}
```

We can specify only a one type of os types like below,

```
$ ansible centos -m ping -o
centos3 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"},"changed": false,"ping": "pong"}
centos1 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"},"changed": false,"ping": "pong"}
centos2 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"},"changed": false,"ping": "pong"}
```

This even works with other types of commands such as listing,

```
$ ansible ubuntu --list-hosts
  hosts (3):
    ubuntu1
    ubuntu2
    ubuntu3
```

Well, this works with regular expressions as well (any number or charters and digits),

```
$ ansible ~.*3 --list-hosts
  hosts (2):
    centos3
    ubuntu3
```

Sometimes it is essential that we run our commands as the root user. In order to perform that, we can specify the `ansible_user` parameter in our inventory file.

```
[centos]
centos1 ansible_user=root
centos2 ansible_user=root
centos3 ansible_user=root

[ubuntu]
ubuntu1
ubuntu2
ubuntu3
```

To verify this, I'm going to use a different module called `command` with the parameter `id` like below,

```
$ ansible all -m command -a 'id' -o
ubuntu2 | CHANGED | rc=0 | (stdout) uid=1000(ansible) gid=1000(ansible) groups=1000(ansible),27(sudo)
ubuntu1 | CHANGED | rc=0 | (stdout) uid=1000(ansible) gid=1000(ansible) groups=1000(ansible),27(sudo)
centos3 | CHANGED | rc=0 | (stdout) uid=0(root) gid=0(root) groups=0(root)
centos2 | CHANGED | rc=0 | (stdout) uid=0(root) gid=0(root) groups=0(root)
centos1 | CHANGED | rc=0 | (stdout) uid=0(root) gid=0(root) groups=0(root)
ubuntu3 | CHANGED | rc=0 | (stdout) uid=1000(ansible) gid=1000(ansible) groups=1000(ansible),27(sudo)
```

Output of the above command shows us that connections to the centos has made as `root` while for ubuntu hosts it still uses the `ansible` user.

This brings us to another interesting scenario. What if we want to connect to our hosts as a normal user, but needs to perform some tasks with escalated user? in that case we can use `ansible_become_true` parameter set to `true` and `ansible_become_pass` set to the password. For example, let's modify our inventory file like below,

```
[centos]
centos1 ansible_user=root
centos2 ansible_user=root
centos3 ansible_user=root

[ubuntu]
ubuntu1 ansible_become=true ansible_become_pass=password
ubuntu2 ansible_become=true ansible_become_pass=password
ubuntu3 ansible_become=true ansible_become_pass=password
```

Next we can execute the same command to see the user being used,

```
$ ansible all -m command -a 'id' -o
centos2 | CHANGED | rc=0 | (stdout) uid=0(root) gid=0(root) groups=0(root)
centos3 | CHANGED | rc=0 | (stdout) uid=0(root) gid=0(root) groups=0(root)
ubuntu1 | CHANGED | rc=0 | (stdout) uid=0(root) gid=0(root) groups=0(root)
centos1 | CHANGED | rc=0 | (stdout) uid=0(root) gid=0(root) groups=0(root)
ubuntu2 | CHANGED | rc=0 | (stdout) uid=0(root) gid=0(root) groups=0(root)
ubuntu3 | CHANGED | rc=0 | (stdout) uid=0(root) gid=0(root) groups=0(root)
```

Ansible by default assumes the ssh port is 22. But there could be situations where the ssh port is different. In that case we can specify the ssh port in one of two ways mentioned in below,

```
[centos]
centos1 ansible_user=root ansible_port=2222
centos2 ansible_user=root
centos3 ansible_user=root

[ubuntu]
ubuntu1 ansible_become=true ansible_become_pass=password
ubuntu2 ansible_become=true ansible_become_pass=password
ubuntu3 ansible_become=true ansible_become_pass=password
```

or

```
[centos]
centos1:2222 ansible_user=root
centos2 ansible_user=root
centos3 ansible_user=root

[ubuntu]
ubuntu1 ansible_become=true ansible_become_pass=password
ubuntu2 ansible_become=true ansible_become_pass=password
ubuntu3 ansible_become=true ansible_become_pass=password
```

And if we ping again, we might be able to connect to the hosts again,

```
$ ansible all -m ping -o
centos2 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"},"changed": false,"ping": "pong"}
ubuntu2 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"}
centos3 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"},"changed": false,"ping": "pong"}
ubuntu1 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"}
centos1 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"},"changed": false,"ping": "pong"}
ubuntu3 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"}
```

We can also specify a control gorup where it will not use and ssh connection.

```
[control]
ubuntu-c ansible_connection=local

[centos]
centos1 ansible_user=root ansible_port=2222
centos2 ansible_user=root
centos3 ansible_user=root

[ubuntu]
ubuntu1 ansible_become=true ansible_become_pass=password
ubuntu2 ansible_become=true ansible_become_pass=password
ubuntu3 ansible_become=true ansible_become_pass=password
```

It is also possible to specify ranges in the inventory files.

```
[control]
ubuntu-c ansible_connection=local

[centos]
centos1 ansible_user=root ansible_port=2222
centos[2:3] ansible_user=root

[ubuntu]
ubuntu[1:3] ansible_become=true ansible_become_pass=password
```

Let's check all the hosts to verify this,

```
$ ansible all --list-hosts
  hosts (7):
    ubuntu-c
    centos1
    centos2
    centos3
    ubuntu1
    ubuntu2
    ubuntu3
```

But still our inventory file has duplicates. For example we specify the ansible user for all groups. This can be addressed using group vars. These group vards will be fed into each record during the execution.

```
[control]
ubuntu-c ansible_connection=local

[centos]
centos1 ansible_port=2222
centos[2:3]

[centos:vars]
ansible_user=root

[ubuntu]
ubuntu[1:3]

[ubuntu:vars]
ansible_become=true
ansible_become_pass=password
```

Let's ping again and see if this works,

```
$ ansible all -m ping -o
centos2 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"},"changed": false,"ping": "pong"}
centos1 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"},"changed": false,"ping": "pong"}
ubuntu-c | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"}
centos3 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"},"changed": false,"ping": "pong"}
ubuntu1 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"}
ubuntu2 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"}
ubuntu3 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"}
```

We can futher group things using a parent:child relationship like below,

```
[control]
ubuntu-c ansible_connection=local

[centos]
centos1 ansible_port=2222
centos[2:3]

[centos:vars]
ansible_user=root

[ubuntu]
ubuntu[1:3]

[ubuntu:vars]
ansible_become=true
ansible_become_pass=password

[linux:children]
centos
ubuntu
```

We can now call everything falling under linux group and see if it still working,

```
$ ansible linux -m ping -o
centos3 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"},"changed": false,"ping": "pong"}
centos2 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"},"changed": false,"ping": "pong"}
ubuntu2 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"}
centos1 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"},"changed": false,"ping": "pong"}
ubuntu1 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"}
ubuntu3 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"}
```

Now let's motify the inventory like below,

```
[control]
ubuntu-c ansible_connection=local

[centos]
centos1 ansible_port=2222
centos[2:3]

[centos:vars]
ansible_user=root

[ubuntu]
ubuntu[1:3]

[ubuntu:vars]
ansible_become=true
ansible_become_pass=password

[linux:children]
centos
ubuntu

[all:vars]
ansible_port=1234
```

You can notice that I've specified variables section for all where I define the ssh port as 1234. This indeed is a wrong port. However, these variables have a precedence effect. Since I have specified the correct port for centos1 host along with it, it will work fine, but others might fail.

```
$ ansible all -m ping -o
centos2 | UNREACHABLE!: Failed to connect to the host via ssh: ssh: connect to host centos2 port 1234: Connection refused
centos3 | UNREACHABLE!: Failed to connect to the host via ssh: ssh: connect to host centos3 port 1234: Connection refused
ubuntu1 | UNREACHABLE!: Failed to connect to the host via ssh: ssh: connect to host ubuntu1 port 1234: Connection refused
ubuntu2 | UNREACHABLE!: Failed to connect to the host via ssh: ssh: connect to host ubuntu2 port 1234: Connection refused
ubuntu3 | UNREACHABLE!: Failed to connect to the host via ssh: ssh: connect to host ubuntu3 port 1234: Connection refused
ubuntu-c | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"}
centos1 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/libexec/platform-python"},"changed": false,"ping": "pong"}
```

It is also possible to write the inventory files in YAML format. But in that case we have to explicitly specify the inveontory file in the `ansible.cfg` file,

```
[defaults]
inventory = hosts.yaml
host_key_checking = False
```

Then we can declare our inventory file in YAML format like below,

```
---
control:
  hosts:
    ubuntu-c:
      ansible_connection: local
centos:
  hosts:
    centos1:
      ansible_port: 2222
    centos2:
    centos3:
  vars:
    ansible_user: root
ubuntu:
  hosts:
    ubuntu1:
    ubuntu2:
    ubuntu3:
  vars:
    ansible_become: true
    ansible_become_pass: password
linux:
  children:
    centos:
    ubuntu:
...
```

Similarly it is also possible to specify the the inventory file in JSON format,

```
{
    "control": {
        "hosts": {
            "ubuntu-c": {
                "ansible_connection": "local"
            }
        }
    },
    "centos": {
        "hosts": {
            "centos1": {
                "ansible_port": 2222
            },
            "centos2": null,
            "centos3": null
        },
        "vars": {
            "ansible_user": "root"
        }
    },
    "ubuntu": {
        "hosts": {
            "ubuntu1": null,
            "ubuntu2": null,
            "ubuntu3": null
        },
        "vars": {
            "ansible_become": true,
            "ansible_become_pass": "password"
        }
    },
    "linux": {
        "children": {
            "centos": null,
            "ubuntu": null
        }
    }
}
```

When using a differnt inventory file formats or multiple file, you can specify the inventory using the `i` flag in the command line,

```
$ ansible all -i hosts.yaml --list-hosts
  hosts (7):
    ubuntu-c
    centos1
    centos2
    centos3
    ubuntu1
    ubuntu2
    ubuntu3
```
