Chapter 3 RH294 RHEL Automation with Ansible

ONLY FOR SELF STUDY, NO COMMERCIAL USAGE!!!


Chapter 3. Managing Variables and Facts

Managing Variables

Variables provide a convenient way to manage dynamic values for a given environment in your Ansible project. Examples of values that variables might contain include:

  • Users to create
  • Packages to install
  • Services to restart
  • Files to remove
  • Archives to retrieve from the internet
Naming Variables

Variable names must start with a letter, and they can only contain letters, numbers, and underscores.

The following table illustrates the difference between invalid and valid variable names.

Table 3.1. Examples of Invalid and Valid Ansible Variable Names

Invalid variable namesValid variable names
web serverweb_server
remote.fileremote_file
1st filefile_1``file1
remoteserver$1remote_server_1 or remote_server1
Defining Variables

The following simplified list shows ways to define a variable, ordered from the lowest precedence to the highest:

  • Group variables defined in the inventory (lowest)

  • Group variables defined in files in a group_vars subdirectory in the same directory as the inventory or the playbook

  • Host variables defined in the inventory

  • Host variables defined in files in a host_vars subdirectory in the same directory as the inventory or the playbook

  • Host facts, discovered at runtime

  • Play variables in the playbook (vars and vars_files)

    • One common method is to place a variable in a vars block at the beginning of a play:

      - hosts: all
        vars:
          user: joe
          home: /home/joe
      or
      
      - hosts: all
        vars_files:
          - vars/users.yml
      
    • Using Variables in Playbooks

      Variables are referenced by placing the variable name in double braces ({{ }}). Ansible substitutes the variable with its value when the task is executed.(quotes are mandatory if the variable is the first element to start)

      vars:
        user: joe n
      
      tasks:
        # This line will read: Creates the user joe
        - name: Creates the user {{ user }}
          user:
            # This line will create the user named Joe
            name: "{{ user }}"
      
  • Task variables

  • Extra variables defined on the command line (highest)

    • by using the --extra-vars or -e option and specifying those variables

    • [user@demo ~]$ ansible-navigator run main.yml -e "package=apache"
      

For the Directories to Populate Host and Group Variables

The following examples illustrate this approach in more detail. Consider a scenario where you need to manage two data centers, and the data center hosts are defined in the ~/project/inventory inventory file:

[datacenter1]
demo1.example.com
demo2.example.com

[datacenter2]
demo3.example.com
demo4.example.com

[datacenters:children]
datacenter1
datacenter2
  • If you need to define a general value for all servers in both data centers, set a group variable for the datacenters host group:

    [admin@station project]$ cat ~/project/group_vars/datacenters
    package: httpd
    
  • If you need to define difference values for each data center, set a group variable for each data center host group:

    [admin@station project]$ cat ~/project/group_vars/datacenter1
    package: httpd
    [admin@station project]$ cat ~/project/group_vars/datacenter2
    package: apache
    
  • If you need to define different values for each managed host in every data center, then define the variables in separate host variable files:

    [admin@station project]$ cat ~/project/host_vars/demo1.example.com
    package: httpd
    [admin@station project]$ cat ~/project/host_vars/demo2.example.com
    package: apache
    [admin@station project]$ cat ~/project/host_vars/demo3.example.com
    package: mariadb-server
    [admin@station project]$ cat ~/project/host_vars/demo4.example.com
    package: mysql-server
    

The directory structure for the example project, project, if it contained all the example files above, would appear as follows:

project
├── ansible.cfg
├── group_vars
│   ├── datacenters
│   ├── datacenters1
│   └── datacenters2
├── host_vars
│   ├── demo1.example.com
│   ├── demo2.example.com
│   ├── demo3.example.com
│   └── demo4.example.com
├── inventory
└── playbook.yml
Using Dictionaries as Variables

For example, consider the following snippet:

user1_first_name: Bob
user1_last_name: Jones
user1_home_dir: /users/bjones
user2_first_name: Anne
user2_last_name: Cook
user2_home_dir: /users/acook

This could be rewritten as a dictionary called users:

users:
  bjones:
    first_name: Bob
    last_name: Jones
    home_dir: /users/bjones
  acook:
    first_name: Anne
    last_name: Cook
    home_dir: /users/acook

You can then use the following variables to access user data:

# Returns 'Bob'
users.bjones.first_name

# Returns '/users/acook'
users.acook.home_dir

Because the variable is defined as a Python dictionary, an alternative syntax is available.

# Returns 'Bob'
users['bjones']['first_name']

# Returns '/users/acook'
users['acook']['home_dir']
Capturing Command Output with Registered Variables

You can use the register statement to capture the output of a command or other information about the execution of a module.

The following play demonstrates how to capture the output of a command for debugging purposes:

---
- name: Installs a package and prints the result
  hosts: all
  tasks:
    - name: Install the package
      ansible.builtin.dnf:
        name: httpd
        state: installed
      register: install_result

    - debug:
        var: install_result

When you run the play, the debug module dumps the value of the install_result registered variable to the terminal.

[user@demo ~]$ ansible-navigator run playbook.yml -m stdout
PLAY [Installs a package and prints the result] ****************************

TASK [setup] ***************************************************************
ok: [demo.example.com]

TASK [Install the package] *************************************************
ok: [demo.example.com]

TASK [debug] ***************************************************************
ok: [demo.example.com] => {
    "install_result": {
        "changed": false,
        "msg": "",
        "rc": 0,
        "results": [
            "httpd-2.4.51-7.el9_0.x86_64 providing httpd is already installed"
        ]
    }
}

PLAY RECAP *****************************************************************
demo.example.com    : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
REFERENCES

How to build your inventory — Ansible Documentation

Using Variables — Ansible Documentation

Variable precedence: Where should I put a variable?

YAML Syntax — Ansible Documentation

Data-variable example
---
- name: Deploy and start Apache HTTPD service
  hosts: webserver
  vars:
    web_pkg: httpd
    firewall_pkg: firewalld
    web_service: httpd
    firewall_service: firewalld
    rule: http

  tasks:
    - name: Required packages are installed and up to date
      ansible.builtin.dnf:
        name:
          - "{{ web_pkg  }}"
          - "{{ firewall_pkg }}"
        state: latest

    - name: The {{ firewall_service }} service is started and enabled
      ansible.builtin.service:
        name: "{{ firewall_service }}"
        enabled: true
        state: started

    - name: The {{ web_service }} service is started and enabled
      ansible.builtin.service:
        name: "{{ web_service }}"
        enabled: true
        state: started

    - name: Web content is in place
      ansible.builtin.copy:
        content: "Example web content"
        dest: /var/www/html/index.html

    - name: The firewall port for {{ rule }} is open
      ansible.posix.firewalld:
        service: "{{ rule }}"
        permanent: true
        immediate: true
        state: enabled

- name: Verify the Apache service
  hosts: workstation
  become: false
  tasks:
    - name: Ensure the webserver is reachable
      ansible.builtin.uri:
        url: http://servera.lab.example.com
        status_code: 200

Managing Secret

  • use the command-line tool named ansible-vault to create, edit, encrypt, decrypt, and view files.
  • Ansible Vault does not implement its own cryptographic functions but rather uses an external Python toolkit. Files are protected with symmetric encryption using AES256 with a password as the secret key. Note that the way this is done has not been formally audited by a third party.
Creating an Encrypted File

Use ansible-vault create *filename* —This command prompts for the new Vault password and then opens a file using the default editor, vi. You can export the EDITOR environment variable to specify a different default editor. To set the default editor to nano, run the export EDITOR=nano.

[student@demo ~]$ ansible-vault create secret.yml
New Vault password: redhat
Confirm New Vault password: redhat

Also can use a Vault password file to store the Vault password with param --vault-password-file=FILE_NAME.

[student@demo ~]$ ansible-vault create --vault-password-file=vault-pass secret.yml
Viewing an Encrypted File

Use the ansible-vault view *filename* command to view an Ansible Vault-encrypted file without opening it for editing.

[student@demo ~]$ ansible-vault view secret1.yml
Vault password: secret
my_secret: "yJJvPqhsiusmmPPZdnjndkdnYNDjdj782meUZcw"
Editing an Existing Encrypted File

Use ansible-vault edit *filename* command. This command decrypts the file to a temporary file and allows you to edit it. When saved, it copies the content and removes the temporary file.

[student@demo ~]$ ansible-vault edit secret.yml
Vault password: redhat

Note

The edit subcommand always rewrites the file, so you should only use it when making changes. This can have implications when the file is kept under version control. You should always use the view subcommand to view the file’s contents without making changes.

Encrypting an Existing File

Use the ansible-vault encrypt *filename* command. This command can take the names of multiple files to be encrypted as arguments.

[student@demo ~]$ ansible-vault encrypt secret1.yml secret2.yml
New Vault password: redhat
Confirm New Vault password: redhat
Encryption successful

Use the --output=OUTPUT_FILE option to save the encrypted file with a new name. You can only use one input file with the --output option.

Decrypting an Existing File

Use the ansible-vault decrypt *filename* command. When decrypting a single file, you can use the --output option to save the decrypted file under a different name.

[student@demo ~]$ ansible-vault decrypt secret1.yml --output=secret1-decrypted.yml
Vault password: redhat
Decryption successful
Changing the Password of an Encrypted File

Use the ansible-vault rekey *filename* command to change the password of an encrypted file. This command can rekey multiple data files at the same time. It prompts for the original password and then the new password.

[student@demo ~]$ ansible-vault rekey secret.yml
Vault password: redhat
New Vault password: RedHat
Confirm New Vault password: RedHat
Rekey successful

When using a Vault password file, use the --new-vault-password-file option:

[student@demo ~]$ ansible-vault rekey \
> --new-vault-password-file=NEW_VAULT_PASSWORD_FILE secret.yml
Playbooks and Ansible Vault

To run a playbook that accesses files encrypted with Ansible Vault, you need to provide the encryption password to the ansible-navigator command. If you do not provide the password, the playbook returns an error:

[student@demo ~]$ ansible-navigator run -m stdout test-secret.yml
ERROR! Attempting to decrypt but no vault secrets found

You can provide the Vault password using one of the following options:

  • Prompt interactively
  • Specify the Vault password file
  • Use the ANSIBLE_VAULT_PASSWORD_FILE environment variable

To provide the Vault password interactively, use --playbook-artifact-enable false (or --pae false) and --vault-id @prompt as illustrated in the following example:

[student@demo ~]$ ansible-navigator run -m stdout --pae false site.yml \
> --vault-id @prompt
Vault password (default): redhat

Or disable the playbook artifact from the following minimal ansible-navigator.yml file:

ansible-navigator:
  playbook-artifact:
    enable: false

Instead of providing the Vault encryption password interactively, you can specify a file that stores the encryption password in plain text by using the --vault-password-file option.

The password must be a string stored as a single line in the file. Because that file contains the sensitive plain text password, it is vital that it be protected through file permissions and other security measures.

[student@demo ~]$ ansible-navigator run -m stdout site.yml \
> --vault-password-file=vault-pw-file

You can also use the ANSIBLE_VAULT_PASSWORD_FILE environment variable to specify the default location of the password file.

Use multiple pass for ansible navigator

To use multiple passwords, pass multiple --vault-id or --vault-password-file options to the ansible-navigator command.

[student@demo ~]$ ansible-navigator run -m stdout --pae false site.yml \
> --vault-id one@prompt --vault-id two@prompt
Vault password (one):
Vault password (two):
...output omitted...

The Vault IDs one and two preceding @prompt can be anything, and you can even omit them entirely. If you use the --vault-id *id* option when you encrypt a file with the ansible-vault command, then the password for the matching ID is the first password tried when running the ansible-navigator command. If it does not match, then ansible-navigator tries the other passwords that you provided. The Vault ID @prompt with no ID is actually shorthand for default@prompt, which means to prompt for the password for Vault ID default.

If you are using multiple Vault passwords with your playbook, make sure that each encrypted file is assigned a Vault ID, and that you enter the matching password with that Vault ID when running the playbook. This ensures that the correct password is selected first when decrypting the vault-encrypted file, which is faster than forcing Ansible to try each of the Vault passwords that you provided until it finds the right one.

Recommended Practices for Variable File Management

Remember that the preferred way to manage group variables and host variables is to create directories at the playbook level. The group_vars directory normally contains variable files with names that match the host groups to which they apply. The host_vars directory normally contains variable files with names that match the hostnames of managed hosts to which they apply.

You can use subdirectories within the group_vars or host_vars directories for each host group or managed host. Those directories can contain multiple variable files, and all of those files are used by the host group or managed host.

In the following example project directory for the playbook.yml playbook, members of the webservers host group use variables in the group_vars/webservers/vars file. The demo.example.com host uses the variables in both the host_vars/demo.example.com/vars and host_vars/demo.example.com/vault files.:

.
├── ansible.cfg
├── group_vars
│   └── webservers
│       └── vars
├── host_vars
│   └── demo.example.com
│       ├── vars
│       └── vault
├── inventory
└── playbook.yml

If you do create subdirectories for each host group or managed host, most variables for demo.example.com can be placed in the vars file, but sensitive variables can be kept secret by placing them in the vault file. You can use ansible-vault to encrypt the vault file and leave the vars file as plain text.

Playbook variables (as opposed to inventory variables) can also be protected with Ansible Vault. You can place sensitive playbook variables in a separate file that is encrypted with Ansible Vault, then include that encrypted variables file in a playbook by using a vars_files directive. This can be useful, because playbook variables take precedence over inventory variables.

Example:

secret.yml was protected by ansible-vault(pass: redhat)

[student@workstation data-secret]$ cat secret.yml 
$ANSIBLE_VAULT;1.1;AES256
36333765303261353032373636386233653935656662613965386230646163396231303364623464
3935303532343139353032333235643831323237633364660a313661303031316263376335306136
66663436313830303766653034313261383962613764356233343163386163353239363633393031
3539626334303834370a666535646135303337323937393438366434333432653639303832663435
36363362626365383534646336363363323135306536623264346461336132323936626538383561
34373266626163623934373736366362326233353630613961313137393235616631646562343961
62633239653635376132663464656265346135636564303535306139363933623831623039346364
37323835333631623130316465366139646135376665303264336165303732363333316363343532
35373230646261303932303837313065353365636364653733386139306166366263666630373830
65303537366636613862346663373762633233633461656230643138633339356666343037626234
323334353065303465663630663738653765

[student@workstation data-secret]$ ansible-vault view secret.yml 
Vault password:
username: ansibleuser1
pwhash: abcdefgh2345690009988767622341123453

Here is a playbookcreate_users.yml which should contain one play (Create user accounts for all our servers in the following example), which uses the variables defined in the secret.yml encrypted file.

Configure the play to use the devservers host group. Run this play as the devops user on the remote managed host. Configure the play to create the ansibleuser1 user defined by the username variable. Set the user’s password using the password hash stored in the pwhash variable.

---
- name: Create user accounts for all our servers
  hosts: devservers
  become: true
  remote_user: devops
  vars_files:
    - secret.yml
  tasks:
    - name: Creating user from secret.yml
      ansible.builtin.user:
        name: "{{ username }}"
        password: "{{ pwhash }}"

Resolve any syntax errors in your playbook before you continue.

[student@workstation data-secret]$ ansible-navigator run -m stdout \
> --pae false create_users.yml --syntax-check --vault-id @prompt
Vault password (default): redhat

playbook: /home/student/data-secret/create_users.yml

Create a password file named vault-pass that contains the password (redhat) and Change the permissions of the file to 0600.

[student@workstation data-secret]$ echo 'redhat' > vault-pass
[student@workstation data-secret]$ chmod 0600 vault-pass

Run the Ansible Playbook to create the ansibleuser1 user on a remote system, using the Vault password in the vault-pass file to decrypt the hashed password for that user. That password is stored as a variable in the secret.yml Ansible Vault encrypted file.

[student@workstation data-secret]$ ansible-navigator run \
> -m stdout create_users.yml --vault-password-file=vault-pass

PLAY [Create user accounts for all our servers] ********************************

TASK [Gathering Facts] *********************************************************
ok: [servera.lab.example.com]

TASK [Creating users from secret.yml] ******************************************
changed: [servera.lab.example.com]

PLAY RECAP *********************************************************************
servera.lab.example.com    : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Verify that the playbook ran correctly. The ansibleuser1 user should exist and have the correct password on the servera.lab.example.com machine.

To make sure that SSH only tries to authenticate by password and not by using an SSH key, use the -o PreferredAuthentications=password option when you log in.

[student@workstation data-secret]$ ssh -o PreferredAuthentications=password \
> ansibleuser1@servera.lab.example.com
ansibleuser1@servera.lab.example.com's password: redhat
...output omitted...
[ansibleuser1@servera ~]$ exit
logout
Connection to servera.lab.example.com closed.
References

Encrypting content with Ansible Vault — Ansible Documentation

Keep vaulted variables safely visible — Ansible Documentation

Managing Facts

Describing Ansible Facts

Ansible facts are variables that are automatically discovered by Ansible on a managed host.

Facts contain host-specific information that can be used just like regular variables in plays, conditionals, loops, or any other statement that depends on a value collected from a managed host.

Some facts gathered for a managed host might include:

  • The host name
  • The kernel version
  • Network interface names
  • Network interface IP addresses
  • Operating system version
  • Number of CPUs
  • Available or free memory
  • Size and free space of storage devices

You can even create custom facts, which are stored on the managed host and are unique to that system.

Facts are a convenient way to retrieve the state of a managed host and to determine what action to take based on that state. For example:

  • Your play might restart a server by using a conditional task based on the value of a fact that was gathered, such as the status of a particular service.
  • The play might customize a MySQL configuration file depending on the available memory that is reported by a fact.
  • The IPv4 address used in a configuration file might be set based on the value of a fact.

Normally, every play runs the ansible.builtin.setup module automatically to gather facts, before it performs its first task.

This is reported as the Gathering Facts task in Ansible 2.3 and later, or simply as setup in earlier versions of Ansible. By default, you do not need to have a task to run ansible.builtin.setup in your play. It is normally run automatically for you.

One way to see what facts are gathered for your managed hosts is to run a short playbook that gathers facts and uses the ansible.builtin.debug module to print the value of the ansible_facts variable.

- name: Fact dump
  hosts: all
  tasks:
    - name: Print all facts
      ansible.builtin.debug:
        var: ansible_facts

When you run the playbook, the facts are displayed in the job output(JSON format):

[user@demo ~]$ ansible-navigator run -m stdout facts.yml

PLAY [Fact dump] ***************************************************************

TASK [Gathering Facts] *********************************************************
ok: [demo1.example.com]

TASK [Print all facts] *********************************************************
ok: [demo1.example.com] => {
    "ansible_facts": {
        "all_ipv4_addresses": [
            "10.30.0.178",
            "172.25.250.10"
        ],
        "all_ipv6_addresses": [
            "fe80::8389:96fd:e53e:979",
            "fe80::cb51:6814:6342:7bbc"
        ],
        "ansible_local": {}
            }
        },
        "apparmor": {
            "status": "disabled"
        },
        "architecture": "x86_64",
        "bios_date": "04/01/2014",
        "bios_vendor": "SeaBIOS",
        "bios_version": "1.13.0-2.module+el8.2.1+7284+aa32a2c4",
        "board_asset_tag": "NA",
        "board_name": "NA",
        "board_serial": "NA",
        "board_vendor": "NA",
        "board_version": "NA",
        "chassis_asset_tag": "NA",
        "chassis_serial": "NA",
        "chassis_vendor": "Red Hat",
        "chassis_version": "RHEL 7.6.0 PC (i440FX + PIIX, 1996)",
        "cmdline": {
            "BOOT_IMAGE": "(hd0,gpt3)/vmlinuz-5.14.0-70.13.1.el9_0.x86_64",
            "console": "ttyS0,115200n8",
            "crashkernel": "1G-4G:192M,4G-64G:256M,64G-:512M",
            "net.ifnames": "0",
            "no_timer_check": true,
            "root": "UUID=fb535add-9799-4a27-b8bc-e8259f39a767"
        },
...output omitted...

The following table shows some facts that might be gathered from a managed node and which might be useful in a playbook:

Table 3.3. Examples of Ansible Facts

FactVariable
Short hostnameansible_facts['hostname']
Fully qualified domain nameansible_facts['fqdn']
Main IPv4 address (based on routing)ansible_facts['default_ipv4']['address']
List of the names of all network interfacesansible_facts['interfaces']
Size of the /dev/vda1 disk partitionansible_facts['devices']['vda']['partitions']['vda1']['size']
List of DNS serversansible_facts['dns']['nameservers']
Version of the currently running kernelansible_facts['kernel']

Note:

Remember that when a variable’s value is a dictionary, one of two syntaxes can be used to retrieve the value. To take two examples from the preceding table:

  • ansible_facts['default_ipv4']['address'] can also be written ansible_facts.default_ipv4.address

When a fact is used in a playbook, Ansible dynamically substitutes the variable name for the fact with the corresponding value:

---
- hosts: all
  tasks:
  - name: Prints various Ansible facts
    ansible.builtin.debug:
      msg: >
        The default IPv4 address of {{ ansible_facts.fqdn }}
        is {{ ansible_facts.default_ipv4.address }}

The following output shows how Ansible was able to query the managed node and dynamically use the system information to update the variable. You can also use facts to create dynamic groups of hosts that match particular criteria.

[user@demo ~]$ ansible-navigator run -m stdout playbook.yml
PLAY [all] ***********************************************************************

TASK [Gathering Facts] *****************************************************
ok: [demo1.example.com]

TASK [Prints various Ansible facts] ****************************************
ok: [demo1.example.com] => {
    "msg": "The default IPv4 address of demo1.example.com is 172.25.250.10\n"
}

PLAY RECAP *****************************************************************
demo1.example.com    : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
Ansible Facts Injected as Variables

Before Ansible 2.5, facts were always injected as individual variables prefixed with the string ansible_ instead of being part of the ansible_facts variable. For example, the ansible_facts['distribution'] fact was called ansible_distribution.

Many playbooks still use facts injected as variables instead of the new syntax, which uses the ansible_facts.* namespace.

One reason why the Ansible community discourages injecting facts as variables is because it risks unexpected collisions between facts and variables. A fact has a very high precedence that overrides playbook and inventory host and group variables, so this can lead to unexpected side effects.

The following table shows some examples of facts with both the ansible_* and ansible_facts.* names.

Table 3.4. Comparison of Selected Ansible Fact Names

ansible_facts.* nameansible_* name
ansible_facts['hostname']ansible_hostname
ansible_facts['fqdn']ansible_fqdn
ansible_facts['default_ipv4']['address']ansible_default_ipv4['address']
ansible_facts['interfaces']ansible_interfaces
ansible_facts['devices']['vda']['partitions']['vda1']['size']ansible_devices['vda']['partitions']['vda1']['size']
ansible_facts['dns']['nameservers']ansible_dns['nameservers']
ansible_facts['kernel']ansible_kernel

Important NOTE:

Currently, Ansible recognizes both the new fact-naming system (using ansible_facts) and the earlier, pre-2.5 “facts injected as separate variables” naming system.

You can disable the ansible_ naming system by setting the inject_facts_as_vars parameter in the [defaults] section of the Ansible configuration file to false. The default setting is currently true.

If it is set to false, you can only reference Ansible facts using the new ansible_facts.* naming system. In that case, attempts to reference facts through the ansible_* namespace results in an error.

Turning off Fact Gathering

Sometimes, you do not want to gather facts for your play. This might be for several reasons:

  • You might not be using any facts and want to speed up the play, or reduce load caused by the play on the managed hosts.
  • The managed hosts perhaps cannot run the ansible.builtin.setup module for some reason, or you need to install some prerequisite software before gathering facts.

To disable fact gathering for a play, set the gather_facts keyword to false:

---
- name: This play does not automatically gather any facts
  hosts: large_datacenter
  gather_facts: false

Even if gather_facts: false is set for a play, you can manually gather facts at any time by running a task that uses the ansible.builtin.setup module:

  tasks:
    - name: Manually gather facts
      ansible.builtin.setup:
Gathering a Subset of Facts

All facts are gathered by default. You can configure the ansible.builtin.setup module to only gather a subset of facts, instead of all facts. For example, to only gather hardware facts, set gather_subset to hardware:

- name: Collect only hardware facts
  ansible.builtin.setup:
    gather_subset:
      - hardware

If you want to gather all facts except a certain subset, add an exclamation point (!) in front of the subset name. Add quotes around the string because in YAML the exclamation point cannot be used at the start of an unquoted string.

- name: Collect all facts except for hardware facts
  ansible.builtin.setup:
    gather_subset:
      - "!hardware"

Visit https://docs.ansible.com/ansible/latest/collections/ansible/builtin/setup_module.html#parameter-gather_subset to view possible values for the gather_subset parameter.

Creating Custom Facts

You can use custom facts to define certain values for managed hosts. Plays can use custom facts to populate configuration files or conditionally run tasks.

Custom facts are stored locally on each managed host. These facts are integrated into the list of standard facts gathered by the ansible.builtin.setup module when it runs on the managed host.

You can statically define custom facts in an INI or JSON file, or you can generate them dynamically when you run a play. Dynamic custom facts are gathered via executable scripts, which generate JSON output.

By default, the ansible.builtin.setup module loads custom facts from files and scripts in the etc/ansible/facts.d directory of each managed host. The name of each file or script must end in .fact for it to be used. Dynamic custom fact scripts must output JSON-formatted facts and must be executable.

The following example static custom facts file is written in INI format. An INI-formatted custom facts file contains a top level defined by a section, followed by the key-value pairs of the facts to define:

[packages]
web_package = httpd
db_package = mariadb-server

[users]
user1 = joe
user2 = jane

You can provide the same facts in JSON format. The following JSON facts are equivalent to the facts specified by the INI format in the preceding example. The JSON data could be stored in a static text file or printed to standard output by an executable script:

{
  "packages": {
    "web_package": "httpd",
    "db_package": "mariadb-server"
  },
  "users": {
    "user1": "joe",
    "user2": "jane"
  }
}

Note:

Custom fact files cannot be in YAML format like a playbook. JSON format is the closest equivalent.

The ansible.builtin.setup module stores custom facts in the ansible_facts['ansible_local'] variable. Facts are organized based on the name of the file that defined them. For example, assume that the /etc/ansible/facts.d/custom.fact file on the managed host produces the preceding custom facts. In that case, the value of ansible_facts['ansible_local']['custom']['users']['user1'] is joe.

You can inspect the structure of your custom facts by gathering facts and using the ansible.builtin.debug module to display the contents of the ansible_local variable with a play similar to the following example:

- name: Custom fact testing
  hosts: demo1.example.com
  gather_facts: true

  tasks:
    - name: Display all facts in ansible_local
      ansible.builtin.debug:
        var: ansible_local

When you run the play, you might see output similar to the following example:

...output omitted...
TASK [Display all facts in ansible_local] *********************************
ok: [demo1.example.com] => {
    "ansible_local": {
        "custom": {
            "packages": {
                "db_package": "mariadb-server",
                "web_package": "httpd"
            },
            "users": {
                "user1": "joe",
                "user2": "jane"
            }
        }
    }
}
...output omitted...

You can use custom facts the same way as default facts in playbooks:

[user@demo ~]$ cat playbook.yml
---
- hosts: all
  tasks:
  - name: Prints various Ansible facts
    ansible.builtin.debug:
      msg: >
           The package to install on {{ ansible_facts['fqdn'] }}
           is {{ ansible_facts['ansible_local']['custom']['packages']['web_package'] }}

[user@demo ~]$ ansible-navigator run -m stdout playbook.yml
PLAY [all] ***********************************************************************

TASK [Gathering Facts] *****************************************************
ok: [demo1.example.com]

TASK [Prints various Ansible facts] ****************************************
ok: [demo1.example.com] => {
    "msg": "The package to install on demo1.example.com is httpd"
}

PLAY RECAP *****************************************************************
demo1.example.com    : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
Creating Variables from Other Variables

Sometimes you might want to create a new variable that uses the value of a different variable. One reason to create a new variable is to minimize typing.

The previous example created custom facts on managed hosts. In that example, you can use the ansible_facts['ansible_local']['custom'] variable to reference those custom facts. That variable has the packages and users keys. If your play contains several references to those custom facts, then you might benefit from creating a new variable.

You can use the ansible.builtin.set_fact module to create a new variable associated to the current host. For example, you might define the custom_host variable and use the ansible_facts['ansible_local']['custom'] variable as its value.

- name: Set custom_host
  ansible.builtin.set_fact:
    custom_host: "{{ ansible_facts['ansible_local']['custom'] }}"

By defining this new variable, your play can use the shorter custom_host['packages'] and custom_host['users'] variables rather than the longer ansible_facts['ansible_local']['custom']['packages'] and ansible_facts['ansible_local']['custom']['users'] variables.

You might also use the ansible.builtin.set_fact module to minimize typing for regular system facts or for registered variables. For example:

- name: Set vda_parts
  ansible.builtin.set_fact:
    vda_parts: "{{ ansible_facts['devices']['vda']['partitions'] }}"

After adding this task, your play can use the vda_parts['vda1']['size'] variable rather than the longer ansible_facts['devices']['vda']['partitions']['vda1']['size'] variable.

Using Magic Variables

Ansible sets some special variables automatically.

These magic variables can also be useful to get information specific to a particular managed host.

Magic variable names are reserved, so you should not define variables with these names.

Four of the most useful magic variables are:

  • hostvars

    Contains the variables for managed hosts, and can be used to get the values for another managed host’s variables. It does not include the managed host’s facts if they have not yet been gathered for that host.

  • group_names

    Lists all groups that the current managed host is in.

  • groups

    Lists all groups and hosts in the inventory.

  • inventory_hostname

    Contains the hostname for the current managed host as configured in the inventory. This might be different from the hostname reported by facts for various reasons.

One way to get insight into their values is to use the ansible.builtin.debug module to display the contents of these variables.

For example, the following task causes every host that runs the play to print out a list of all network interfaces on the demo2.example.com host. This task works as long as facts were gathered for demo2 earlier in the play or by a preceding play in the playbook. It uses the hostvars magic variable to access the ansible_facts['interfaces'] fact for that host.

    - name: Print list of network interfaces for demo2
      ansible.builtin.debug:
        var: hostvars['demo2.example.com']['ansible_facts']['interfaces']

You can use the same approach with regular variables, not only facts. Keep in mind that the preceding task is run by every host in the play, so it would be more efficient to use a different module to apply information gathered from one host to the configuration of each of those other managed hosts.

Remember that you can use the ansible.builtin.setup module in a task to refresh gathered facts at any time. However, fact gathering does cause your playbook to take longer to run.

Several other magic variables are also available. For more information, see https://docs.ansible.com/ansible/latest/reference_appendices/special_variables.html.

References

Ansible facts - Ansible Documentation

ansible.builtin.setup module - Gathers facts about remote hosts - Ansible Documentation

Special Variables - Ansible Documentation

Chapter 3 TEST

Instructions

  1. In the working directory, create the playbook.yml playbook. In the playbook, start creating a play to install and configure the web server hosts with an Apache HTTP Server that has basic authentication enabled. Configure the webserver host group to contain the managed hosts for the play.

    Define the following play variables:

    VariableValues
    firewall_pkgfirewalld
    firewall_svcfirewalld
    web_pkghttpd
    web_svchttpd
    ssl_pkgmod_ssl
    httpdconf_srcfiles/httpd.conf
    httpdconf_dest/etc/httpd/conf/httpd.conf
    htaccess_srcfiles/.htaccess
    secrets_dir/etc/httpd/secrets
    secrets_srcfiles/htpasswd
    secrets_dest"{{ secrets_dir }}/htpasswd"
    web_root/var/www/html
  2. Add a tasks section to the play. Write a task that ensures the latest version of the necessary packages are installed. These packages are defined by the firewall_pkg, web_pkg, and ssl_pkg variables.

  3. Add a second task to the play that ensures that the file specified by the httpdconf_src variable has been copied (with the ansible.builtin.copy module) to the location specified by the httpdconf_dest variable on the managed host. The file must be owned by the root user and the root group. Set 0644 as the file permissions.

  4. Add a third task that uses the ansible.builtin.file module to create the directory specified by the secrets_dir variable on the managed host. This directory holds the password files used for the basic authentication of web services. The directory must be owned by the apache user and the apache group. Set 0500 as the directory permissions.

  5. Add a fourth task that uses the ansible.builtin.copy module to add an htpasswd file, used for basic authentication of web users. The source should be defined by the secrets_src variable. The destination should be defined by the secrets_dest variable. The file must be owned by the apache user and group. Set 0400 as the file permissions.

  6. Add a fifth task that uses the ansible.builtin.copy module to create a .htaccess file in the document root directory of the web server. Copy the file specified by the htaccess_src variable to {{ web_root }}/.htaccess. The file must be owned by the apache user and the apache group. Set 0400 as the file permissions.

  7. Add a sixth task that uses the ansible.builtin.copy module to create the web content file, index.html, in the directory specified by the web_root variable. The file should contain the message *HOSTNAME* (*IPADDRESS*) has been customized by Ansible., where HOSTNAME is the fully qualified host name of the managed host and IPADDRESS is its IPv4 IP address. Use the content option with the ansible.builtin.copy module to specify the content of the file, and Ansible facts to specify the host name and IP address.

  8. Add a seventh task that uses the ansible.builtin.service module to enable and start the firewall service on the managed host.

  9. Add an eighth task that uses the ansible.posix.firewalld module to enable access to the https service that is needed for users to access web services on the managed host. This firewall change should be permanent and should take place immediately.

  10. Add a final task that uses the ansible.builtin.service module to enable and start the web service on the managed host for all configuration changes to take effect. The name of the web service is defined by the web_svc variable.

  11. Define a second play in the playbook.yml file that uses the workstation machine as the managed host to test authentication to the web server. It does not need privilege escalation. Define a variable named web_user with the value guest.

  12. Add a directive to the play that adds additional variables from a variable file named vars/secret.yml. This file contains a variable named web_pass that specifies the password for the web user. You create this file later in the lab.

    Define the start of the task list.

  13. Add two tasks to the second play.

    The first task uses the ansible.builtin.uri module to request content from https://serverb.lab.example.com using basic authentication. Use the web_user and web_pass variables to authenticate to the web server. The task should verify a return HTTP status code of 200. Register the task result in a variable named auth_test.

    Note that the certificate presented by serverb is not trusted, so you need to avoid certificate validation.

    The second task uses the ansible.builtin.debug module to print the content returned from the web server, which is contained in the auth_test variable.

  14. Create a vars/secret.yml file, encrypted with Ansible Vault. Use the password redhat to encrypt it. It should set the web_pass variable to redhat, which is the web user’s password.

    1. Create a subdirectory named vars in the working directory.

      [student@workstation data-review]$ mkdir vars
      
    2. Create the encrypted variable file, vars/secret.yml, using Ansible Vault. Set the password for the encrypted file to redhat.

      [student@workstation data-review]$ ansible-vault create vars/secret.yml
      New Vault password: redhat
      Confirm New Vault password: redhat
      
    3. Add the following variable definition to the file:

      web_pass: redhat
      
    4. Save and close the file.

  15. Run the playbook.yml playbook. Verify that content is successfully returned from the web server, and that it matches what was configured in an earlier task.

playbook.yml

---
- name: Webserver with variable test
  hosts: webserver
  vars:
    firewall_pkg: firewalld
    firewall_svc: firewalld
    web_pkg: httpd
    web_svc: httpd
    ssl_pkg: mod_ssl
    httpdconf_src: files/httpd.conf
    httpdconf_dest: /etc/httpd/conf/httpd.conf
    htaccess_src: files/.htaccess
    secrets_dir:  /etc/httpd/secrets
    secrets_src:  files/htpasswd
    secrets_dest: "{{ secrets_dir }}/htpasswd"
    web_root: /var/www/html

  tasks:
    - name: Installing latest packages for PKG
      ansible.builtin.dnf:
        name:
          - "{{ firewall_pkg }}"
          - "{{ web_pkg }}"
          - "{{ ssl_pkg }}"
        state: latest

    - name: Copying FILES
      ansible.builtin.copy:
        src: "{{ httpdconf_src }}"
        dest: "{{ httpdconf_dest }}"
        owner: root
        group: root
        mode: 0644

    - name: Creating Secret folder
      ansible.builtin.file:
        path: "{{ secrets_dir }}"
        state: directory
        owner: apache
        group: apache
        mode: 0500

    - name: Coping secret files
      ansible.builtin.copy:
        src: "{{ secrets_src }}"
        dest: "{{ secrets_dest }}"
        owner: apache
        group: apache
        mode: 0400

    - name: Coping htaccess files
      ansible.builtin.copy:
        src: "{{ htaccess_src }}"
        dest: "{{ web_root }}/.htaccess"
        owner: apache
        group: apache
        mode: 0400

    - name: Coping webcontent files
      ansible.builtin.copy:
        dest: "{{ web_root }}/index.html"
        content: >
          {{ ansible_facts['fqdn'] }}({{ansible_facts['default_ipv4']['address']}}) has been customized by Ansible.

    - name: Starting firewall service
      ansible.builtin.service:
        name: "{{ firewall_svc }}"
        state: started
        enabled: true

    - name: Enable httpd access through firewall
      ansible.posix.firewalld:
        service: https
        permanent: true
        immediate: true
        state: enabled

    - name: Starting web service
      ansible.builtin.service:
        name: "{{ web_svc }}"
        state: started
        enabled: true

- name: Testing webservice
  hosts: workstation
  become: false
  vars:
    web_user: guest

  vars_files:
    - vars/secret.yml

  tasks:
    - name: Accessing the web service
      ansible.builtin.uri:
        url: https://serverb.lab.example.com
        force_basic_auth: true
        user: "{{ web_user }}"
        password: "{{ web_pass }}"
        validate_certs: false
        return_content: true
        status_code: 200

      register: auth_test

    - name: debug info
      ansible.builtin.debug:
        var: auth_test['content']

result:

[student@workstation data-review]$ ansible-navigator run -m stdout playbook.yml --pae false --vault-id @prompt
Vault password (default): 

PLAY [Webserver with variable test] ******************************************************************

TASK [Gathering Facts] *******************************************************************************
ok: [serverb.lab.example.com]

TASK [Installing latest packages for PKG] ************************************************************
ok: [serverb.lab.example.com]

TASK [Copying FILES] *********************************************************************************
ok: [serverb.lab.example.com]

TASK [Creating Secret folder] ************************************************************************
ok: [serverb.lab.example.com]

TASK [Coping secret files] ***************************************************************************
ok: [serverb.lab.example.com]

TASK [Coping htaccess files] *************************************************************************
ok: [serverb.lab.example.com]

TASK [Coping webcontent files] ***********************************************************************
ok: [serverb.lab.example.com]

TASK [Starting firewall service] *********************************************************************
ok: [serverb.lab.example.com]

TASK [Enable httpd access through firewall] **********************************************************
ok: [serverb.lab.example.com]

TASK [Starting web service] **************************************************************************
ok: [serverb.lab.example.com]

PLAY [Testing webservice] ****************************************************************************

TASK [Gathering Facts] *******************************************************************************
ok: [workstation]

TASK [Accessing the web service] *********************************************************************
ok: [workstation]

TASK [debug info] ************************************************************************************
ok: [workstation] => {
    "auth_test['content']": "\"serverb.lab.example.com\" (172.25.250.11) has been customized by Ansible.\n        \n"
}

PLAY RECAP *******************************************************************************************
serverb.lab.example.com    : ok=10   changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
workstation                : ok=3    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   

TO BE CONTINUED…

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值