r0k5t4r
r0k5t4r

Reputation: 21

Ansible: Change default values of nested vars

I have written a role to create users. Now I wanted to modify the role to also give the option to remove users. What looked very simple in the beginning is now giving me constant headaches.

Here is my role:

---
- name: Dictionary playbook example
  hosts: localhost
  vars:
    my_default_values:
        users: &def
            state: 'present'
            force: 'yes'
            remove: 'no'
    my_values:
        users:
            - username: bla
              <<: *def
              state: absent
            - username: bla2
              <<: *def

  tasks:
    - debug: var=my_default_values
    - debug: var=my_values

Here is the output:

PLAY [Dictionary playbook example] ********************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************
        
        TASK [Gathering Facts] ********************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************
        ok: [localhost]
        
        TASK [debug] ******************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************
        ok: [localhost] => {
            "my_default_values": {
                "users": {
                    "force": "yes",
                    "remove": "no",
                    "state": "present"
                }
            }
        }
        
        TASK [debug] ******************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************
        ok: [localhost] => {
            "my_values": {
                "users": [
                    {
                        "force": "yes",
                        "remove": "no",
                        "state": "absent",
                        "username": "bla"
                    },
                    {
                        "force": "yes",
                        "remove": "no",
                        "state": "present",
                        "username": "bla2"
                    }
                ]
            }
        }
        
PLAY RECAP ********************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************************
localhost                  : ok=3    changed=0    unreachable=0    failed=0    skipped=0
rescued=0    ignored=0

This is working fine as long as I always define <<: *def below every - username in my role. But this is problematic if let's say I want to have the users variables defined in an external file users.yml. I guess I would need some kind of loop?

In the end I want to use the role with Katello and so the users are defined inside of Katello. This is why I need to get this working in a more dynamic fashion.

Upvotes: 2

Views: 1065

Answers (3)

r0k5t4r
r0k5t4r

Reputation: 21

My role finally works. Maybe it is not the best solution, but it does work just fine for what I wanted to achieve:

---
# tasks file for securehost

- set_fact:
    users_with_default: >-
      {{
        users_with_default | default([])
        + [user_defaults | combine(item)]
      }}
  loop: "{{ users }}"
  vars:
    user_defaults:
      state: 'present'
      force: 'yes'
      remove: 'no'

- name: Allow 'wheel' group to have passwordless sudo
  lineinfile:
    dest: /etc/sudoers
    state: present
    regexp: '^%wheel'
    line: '%wheel ALL=(ALL) NOPASSWD: ALL'
    validate: 'visudo -cf %s'

- name: create base homedir /lhome with mode 775
  file:
    path: /lhome
    state: directory
    mode: "u=rwx,g=rx,o=rx"

- name: Add / Remove users
  environment: umask=077
  user: name={{ item.username }} uid={{ item.uid }} home="/lhome/{{ item.username }}" groups={{ item.groups }} state={{ item.state }} remove={{ item.remove }} force={{ item.force }}
  with_items: "{{ users_with_default }}"

- name: change mode of all homedirs under /lhome to mode 0700
  file:
    path: "/lhome/{{ item.username }}"
    state: directory
    mode: 0700
  with_items: "{{ users_with_default }}"
  when:
    - item.state != 'absent'

- name: Add authorized keys for users
  authorized_key: user={{ item.username }} key="{{ item.key }}" exclusive={{ pubkey_exclusive }}
  with_items: "{{ users_with_default }}"
  when:
    - item.state != 'absent'

- name: Change root password
  user:
   name: root
   password: "{{ root_password_hash }}"
  when: change_root_pw | bool

Again thanks a lot for your quick help on this.

Upvotes: 0

Vladimir Botka
Vladimir Botka

Reputation: 68254

Use module_defaults. It's not necessary to include state: present and remove: no because these are defaults. In the list my_users include only required parameter name and parameters with non-default values

- hosts: localhost
  module_defaults:
    ansible.builtin.user:
      force: yes
  vars:
    my_users:
      - name: foo
        state: absent
      - name: bar

In the task, by default omit optional parameters that you want to use

  tasks:
    - ansible.builtin.user:
        name: "{{ item.name }}"
        state: "{{ item.state|default(omit) }}"
        force: "{{ item.force|default(omit) }}"
        remove: "{{ item.remove|default(omit) }}"
      loop: "{{ my_users }}"

Upvotes: 1

β.εηοιτ.βε
β.εηοιτ.βε

Reputation: 39304

I can see two approaches:

If you plan on looping on those users and use the values of the dictionaries, then, just define the adequate default with the purposed default filter:

roles/demo/tasks/main.yml

- debug:
    msg:
      user:
        username: "{{ item.username }}"
        state: "{{ item.state | default('present') }}"
        force: "{{ item.force | default('yes') }}"
        remove: "{{ item.remove | default('no') }}"
  loop: "{{ users }}"
  loop_control:
    label: "{{ item.username }}"

Playbook:

- hosts: localhost
  gather_facts: no

  roles:
    - name: demo
      users:
        - username: bla
          state: absent
        - username: bla2

Which will yield:

ok: [localhost] => (item=bla) => 
  msg:
    user:
      force: 'yes'
      remove: 'no'
      state: absent
      username: bla
ok: [localhost] => (item=bla2) => 
  msg:
    user:
      force: 'yes'
      remove: 'no'
      state: present
      username: bla2

If you really do need a list inside your role, recreate a new list, combining the the default value to the input given to the role, taking advantage of the fact that, when combining two dictionaries containing the same key, the values of the second dictionary will override the values of the first one:

roles/demo/tasks/main.yml

- set_fact:
    users_with_default: >-
      {{
        users_with_default | default([])
        + [user_defaults | combine(item)]
      }}
  loop: "{{ users }}"
  vars:
    user_defaults:
      state: 'present'
      force: 'yes'
      remove: 'no'

- debug:
    var: users_with_default

Playbook:

- hosts: localhost
  gather_facts: no

  roles:
    - name: demo
      users:
        - username: bla
          state: absent
        - username: bla2

Which will yield:

ok: [localhost] => 
  users_with_default:
  - force: 'yes'
    remove: 'no'
    state: absent
    username: bla
  - force: 'yes'
    remove: 'no'
    state: present
    username: bla2

Upvotes: 1

Related Questions