Bookmark this page

Guided Exercise: Templating External Data Using Lookups

  • Use the lookup and query functions to template data from external sources into playbooks and deployed template files.

Outcomes

  • Identify different sources of information in Ansible Playbooks.

  • Use existing lookup plug-ins to retrieve information.

As the student user on the workstation machine, use the lab command to prepare your system for this exercise.

This command creates the Git repository needed for the exercise.

[student@workstation ~]$ lab start data-lookups

Procedure 7.2. Instructions

  1. Clone the https://git.lab.example.com/student/data-lookups.git Git repository into the /home/student/git-repos directory and then create a branch for this exercise.

    1. From a terminal, create the /home/student/git-repos directory if it does not already exist, and then change into it.

      [student@workstation ~]$ mkdir -p ~/git-repos/
      [student@workstation ~]$ cd ~/git-repos/
    2. Clone the https://git.lab.example.com/student/data-lookups.git repository and then change directory to the cloned repository:

      [student@workstation git-repos]$ git clone \
      > https://git.lab.example.com/student/data-lookups.git
      Cloning into 'data-lookups'...
      ...output omitted...
      [student@workstation git-repos]$ cd data-lookups
    3. Create the exercise branch.

      [student@workstation data-lookups]$ git checkout -b exercise
      Switched to a new branch 'exercise'
  2. Populate site.yml with tasks to create groups using YAML-formatted data from the groups.yml file.

    1. Review the groups.yml file.

      ---
      - name: devs
        members:
          - user1
          - user2
      - name: ops
        members:
          - user3
    2. You can read the contents of a YAML-formatted file in several ways. In the "Load group information" task from the site.yml playbook, you use the ansible.builtin.file lookup plug-in to read the file, and the from_yaml filter to load its data into the user_groups fact as structured YAML so that Ansible can parse it later:

      "{{ lookup('ansible.builtin.file', 'groups.yml') | from_yaml }}"

      The completed task should consist of the following content:

          - name: Load group information
            ansible.builtin.set_fact:
              user_groups: "{{ lookup('ansible.builtin.file', 'groups.yml') | from_yaml }}"
    3. Complete the "Create groups" task to create each group specified by the YAML data in the user_groups fact. The task must use the ansible.builtin.group module, looping on the user_groups fact. Use item.name to specify the group name of the group to create for each iteration of the loop. The completed task should consist of the following content:

          - name: Create groups
            ansible.builtin.group:
              name: "{{ item['name'] }}"
              state: present
            loop: "{{ user_groups }}"
  3. Complete the "Create users" task.

    1. Use the lines lookup plug-in to read user information from the users.txt file.

      "{{ query('ansible.builtin.lines', 'cat users.txt') }}"

      The lines plug-in reads the output of cat users.txt as individual lines. The query function ensures that the data is a list.

      Configure the task to loop through the users in this list.

      Use the loop keyword to configure the task to loop through the users in this list:

          - name: Create users
            ansible.builtin.debug:
              msg: "To be done"
            loop: "{{ query('ansible.builtin.lines', 'cat users.txt') }}"
    2. When you create the users, create a random password as well. The password lookup plug-in generates random passwords and optionally stores the passwords in a local file.

      The previous step added a loop on the list of usernames from users.txt; you can use the same item variable to generate the associated file for each user.

      "{{ lookup('ansible.builtin.password', 'credentials/' + item + ' length=9') }}"

      Store the password into a task variable to make it easier to use. Ensure that you include the space after the first single quote in the ' length=9' argument. The task should now consist of the following content:

          - name: Create users
            vars:
              password_plain: "{{ lookup('ansible.builtin.password', 'credentials/' + item + ' length=9') }}"
            ansible.builtin.debug:
              msg: "To be done"
            loop: "{{ query('ansible.builtin.lines', 'cat users.txt') }}"
    3. Replace the ansible.builtin.debug module with the ansible.builtin.user module.

      You need to hash the new password before you can use it. You also need to set update_password: on_create for the module.

      Important

      The update_password: on_create option only sets the password if the task creates a new user on the managed host. If the user already exists but the password specified by the playbook is different from the one already set for the user on the system, then Ansible does not change the user's password.

      This helps ensure that Ansible does not change an existing user's password if that user has already changed it but you run the playbook a second time.

          - name: Create users
            vars:
              password_plain: "{{ lookup('ansible.builtin.password', 'credentials/' + item + ' length=9') }}"
            ansible.builtin.user:
              name: "{{ item }}"
              password: "{{ password_plain | password_hash('sha512') }}"
              update_password: on_create
              state: present
            loop: "{{ query('ansible.builtin.lines', 'cat users.txt') }}"
    4. The last task uses the subelements filter to populate the members of each group. However, no lookup plug-ins are used. Confirm that the completed play consists of the following content:

      - name: Populate users and groups
        hosts: all
        gather_facts: false
        tasks:
      
          - name: Load group information
            ansible.builtin.set_fact:
              user_groups: "{{ lookup('ansible.builtin.file', 'groups.yml') | from_yaml }}"
      
          - name: Create groups
            ansible.builtin.group:
              name: "{{ item['name'] }}"
              state: present
            loop: "{{ user_groups }}"
      
          - name: Create users
            vars:
              password_plain: "{{ lookup('ansible.builtin.password', 'credentials/' + item + ' length=9') }}"
            ansible.builtin.user:
              name: "{{ item }}"
              password: "{{ password_plain | password_hash('sha512') }}"
              update_password: on_create
              state: present
            loop: "{{ query('ansible.builtin.lines', 'cat users.txt') }}"
      
          - name: Add users to groups
            ansible.builtin.user:
              name: '{{ item[1] }}'
              groups: "{{ item[0]['name'] }}"
              append: true
            loop: "{{ user_groups | subelements('members', 'skip_missing=true') }}"
  4. Run the playbook and verify that the users, groups, and group members are configured correctly.

    1. Use the ansible-navigator run command to run the playbook.

      [student@workstation data-lookups]$ ansible-navigator run \
      > -m stdout site.yml
      
      PLAY [Populate users and groups] ***********************************************
      
      TASK [Load group information] **************************************************
      ok: [serverf.lab.example.com]
      
      TASK [Create groups] ***********************************************************
      changed: [serverf.lab.example.com] => (item={'name': 'devs', 'members': ['user1', 'user2']})
      changed: [serverf.lab.example.com] => (item={'name': 'ops', 'members': ['user3']})
      
      TASK [Create users] ************************************************************
      changed: [serverf.lab.example.com] => (item=user1)
      changed: [serverf.lab.example.com] => (item=user2)
      changed: [serverf.lab.example.com] => (item=user3)
      
      TASK [Add users to groups] *****************************************************
      changed: [serverf.lab.example.com] => (item=[{'name': 'devs', 'members': ['user1', 'user2']}, 'user1'])
      changed: [serverf.lab.example.com] => (item=[{'name': 'devs', 'members': ['user1', 'user2']}, 'user2'])
      changed: [serverf.lab.example.com] => (item=[{'name': 'ops', 'members': ['user3']}, 'user3'])
      
      PLAY RECAP *********************************************************************
      serverf.lab.example.com    : ok=4    changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
    2. Verify that the users have been created on the serverf server.

      [student@workstation data-lookups]$ ssh serverf "tail -n3 /etc/passwd"
      user1:x:1002:1004::/home/user1:/bin/bash
      user2:x:1003:1005::/home/user2:/bin/bash
      user3:x:1004:1006::/home/user3:/bin/bash
    3. Verify that the groups exist and have the relevant members.

      [student@workstation data-lookups]$ ssh serverf "tail -n5 /etc/group"
      devs:x:1002:user1,user2
      ops:x:1003:user3
      user1:x:1004:
      user2:x:1005:
      user3:x:1006:

Finish

On the workstation machine, change to the student user home directory and use the lab command to complete this exercise. This step is important to ensure that resources from previous exercises do not impact upcoming exercises.

[student@workstation ~]$ lab finish data-lookups

This concludes the section.

Revision: do374-2.2-82dc0d7