I'm writing a playbook to manage users on our servers defined in users.yml:
---
users:
- login: ab
full_login: abcdef
name: Aaaa Bbbb,,,
admin_on: server1, server2
regular_on: server3
active: yes
I would like to include some protection from a situation when there will be two different users with the same login defined. The playbook looks like this:
---
- name: Provision users on servers
hosts: all
remote_user: morty
become: yes
vars_files:
- users.yml
tasks:
- name: Create users
user:
name: "{{ item.login }}"
comment: "{{ item.name }}"
update_password: on_create
with_items:
- "{{ users }}"
when: ???
What is the recommended course of action? Should I create another list that will keep track of already processed logins or is there a better way?
Use assertion task to make preflight checks at the very beginning of your playbook:
- name: Safety check
assert:
that: >
users | map(attribute='login') | list | count
==
users | map(attribute='login') | list | unique | count
In this case we check that the length of original list of logins is the same as of list with unique logins.
Related
I am attempting to validate a Cisco configuration with Ansible. I want to be able to tell whether any users have been configured other than the valid ones.
Valid users:
username admin,
username readonly
Invalid users:
username secretbackdoor
I have tried to create a list of users, then flag any which are not valid. The code i have so far is as follows:
---
- hosts: cisco
gather_facts: no
tasks:
- name: show run
ios_command:
commands:
- show run
register: cisco_show_run
- name: list_cisco_usernames
set_fact: cisco_usernames="{{ cisco_show_run.stdout[0] | regex_findall('username (\S+)', multiline=True) }}"
- name: print usernames
debug:
msg: {{ item }}
with_items: "{{ cisco_usernames }}"
This will print out the three users. Not sure where to go next.
"Set Theory Filters" might be next option. For example
- hosts: localhost
vars:
valid_users: [admin, readonly]
invalid_users: [secretbackdoor]
cisco_usernames: [admin, readonly, secretbackdoor]
tasks:
- name: Display users not in valid_users
debug:
msg: Not among valid users {{ not_valid }}
when: not_valid|length > 0
vars:
not_valid: "{{ cisco_usernames|difference(valid_users) }}"
- name: Display users in invalid_users
debug:
msg: Among invalid users {{ not_valid }}
when: not_valid|length > 0
vars:
not_valid: "{{ cisco_usernames|intersect(invalid_users) }}"
gives (abridged)
ok: [localhost] =>
msg: Not among valid users ['secretbackdoor']
ok: [localhost] =>
msg: Among invalid users ['secretbackdoor']
Thanks for this. Your solution is working fine. I put in the first option, as I do not always know what the 'incorrect' users are.
I have a setup consisting of prod and dev environments
there are 2 projects ( project1 and project2 )
I have dev's and ops users. Devs to only be created on dev servers in projects that the user is assigned and ops's to be created in all projects and envs.
I'd like for all users to be defined in the same user definition file.
my user definitions :
- username:
profile: # dev / ops
projects: # project1 / project2 / all
key: #"ssh-rsa key
OSgroups: "" # which OS groups is user member of
OSpass: "" # hashed OS password
my user create playbook:
- name: Create users
become: yes
user:
name={{ item.username }}
shell={{ item.shell }}
groups={{ item.groups }}
createhome=yes
password={{ item.OSpass }}
## now the problem part
with_items:
- "{{ users }}"
when: "{{ defaults_for_env.environment }} == {{ item.profile }}"
##
------------------------------------------------------------
## environment defaults
---
defaults_for_env:
- environment: "dev"
when just running usercreate playbook users are created, so the commands work.
What I'd like is for the playbook to:
for host is in inventory group [development] to create dev's assigned to inventory group [project1] and all users of type ops.
And for hosts in inventory group [prod] to only create users of type ops.
I cant get my head around the loops and inventory'n'stuff
Hope my question makes sense ?
One possible solution to your current requirement.
Inventory
---
all:
children:
dev:
hosts:
devhost1:
devhost2:
prod:
hosts:
prodhost1:
prodhost2
group_vars/all.yaml
---
#....
default_users:
- name: opsuser1
shell: /bin/bash
groups:
- group1
- group2
createhome: true
password: S0S3cr3t
- name: opsuser2
shell: /bin/sh
groups:
- wheel
- docker
- users
createhome: false
password: n0ts0S3cr3t
users: "{{ default_users + (specific_users | default([])) }}"
group_vars/dev.yml
---
#....
specific_users:
- name: devuser1
shell: /bin/bash
groups:
- groupa
- groupb
createhome: true
password: v3rYS3cr3t
- name: devuser2
shell: /bin/sh
groups:
- titi
- toto
- tata
createhome: false
password: U1trAS3cr3t
Your playbook
- hosts: all
become: true
tasks:
- name: Create users
user:
name: "{{ item.username }}"
shell: "{{ item.shell }}"
groups: "{{ item.groups }}"
createhome: "{{ item.createhome | bool }}"
password: ""{{ item.password | password_hash('sha512', 'S3cretS4lt') }}"
loop: "{{ users | flatten(levels=1) }}"
The playbook will go over all your hosts. By default it will read the values in the all group where you have the definition of default_users (i.e. ops) + the calculation for the users list being default_users + specific_users.
For machines in the prod group, specific_users is null and will default to an empty list.
For machines in the dev group, specific_users will be added to the default ones.
The loop is then made on users which will have the correct values for each machine depending on its situation.
I need to somehow loop over a list of variables and execute both of the below roles once for each iteration, on each iteration passing a variable to the role. For example, given a variable list of 100-101, I need to execute in the order role1:100, role2:100, role1:101, role2:101. The variables 100-100 should be passed to the tasks inside the role.
---
- hosts: group1
tasks:
- include_role:
name: role1
- hosts: group2
tasks:
- include_role:
name: role2
I was looking at the below answer as a possible solution but I am not sure how to adapt it to my needs. Can the above scenario be accomplished in Ansible?
Ansible: How to iterate over a role with an array?
Loop over the Cartesian product of variables and role names:
vars:
roles_to_include:
- role1
- role2
values_to_pass:
- 100
- 101
tasks:
- include_role:
name: "{{ item.1 }}"
vars:
my_variable: "{{ item.0 }}"
loop: "{{ values_to_pass | product(roles_to_include) | list }}"
I have a set of roles that are all unique and use a common role that is pulled in via a dependency to perform a bunch of the same actions that all these other roles need to have done. I need to be able to pass into a specific role a parameter to say in this case pull a docker image from a registry, in that other case save the image out and do some other things. Variables cause issues as they are explicit for a host and I might have several roles per host. How can I structure my ansible playbook to do this?
Example:
I have a common role that is pulled into other roles as a dependency:
---
- name: Included Playbook - What vars do I see
debug:
msg: "Name Pull: {{ imagename }}"
- name: Local Running
debug:
msg: "Local option selected"
when: localimage == "true"
- name: Not local
debug:
msg: "Not Local remote"
when: localimage == "false"
Then the primary role tasks\main.yml
---
- name: Included Playbook - What vars do I see from Primary
vars:
myname: "{{ imagemainname }}"
debug:
msg: "Name-primary: {{ myname }}"
and its meta\main.yml
---
dependencies:
- { role: image-pull, imagename: "{{ imagemainname }}" }
This is the same for a second role
---
- name: Included Playbook - What vars do I see from Second
vars:
myname: "{{ secondname }}"
debug:
msg: "Name-second: {{ myname }}"
and its meta\main.yml
---
dependencies:
- { role: image-pull, imagename: "{{ secondname }}" }
My main playbook calls both primary and second roles and the role specific vars works fine.
---
- name: Master
hosts: imagemaster
remote_user: root
vars:
imagemainname: "Top Dog"
roles:
- image-master
- name: Second
hosts: second
remote_user: root
vars:
imagemainname: "Second Dog"
roles:
- second
What doesn't work is when I want to state the do option a or b in the "pulled" role.
If my inventory file looks like this:
[imagemaster]
127.0.0.1
[imagemaster:vars]
localimage=false
[second]
127.0.0.1
[second:vars]
localimage=true
It doesn't work as whatever is the last entry for localimage is what all roles will use.
What can I do to pass in something from the inventory/host_vars/etc that means my playbook doesn't change for every iteration in this setup?
If you plan to apply primary and secondary role to the same host (as in your example (127.0.0.1), then you have no options:
Within any section, redefining a var will overwrite the previous instance. If multiple groups have the same variable, the last one loaded wins. If you define a variable twice in a play’s vars: section, the 2nd one wins.
(from the docs)
If you plan to apply them to different hosts, then test is properly, e.g.:
[imagemaster]
127.0.0.1
[imagemaster:vars]
localimage=false
[second]
127.0.1.1
[second:vars]
localimage=true
In this scenario, when role (primary/secondary) is applied to imagemaster group, then localimage=false; when applied to second group – localimage=true.
I'm customizing linux users creation inside my role. I need to let users of my role customize home_directory, group_name, name, password.
I was wondering if there's a more flexible way to cope with default values.
I know that the code below is possible:
- name: Create default
user:
name: "default_name"
when: my_variable is not defined
- name: Create custom
user:
name: "{{my_variable}}"
when: my_variable is defined
But as I mentioned, there's a lot of optional variables and this creates a lot of possibilities.
Is there something like the code above?
user:
name: "default_name", "{{my_variable}}"
The code should set name="default_name" when my_variable isn't defined.
I could set all variables on defaults/main.yml and create the user like that:
- name: Create user
user:
name: "{{my_variable}}"
But those variables are inside a really big hash and there are some hashes inside that hash that can't be a default.
You can use Jinja's default:
- name: Create user
user:
name: "{{ my_variable | default('default_value') }}"
Not totally related, but you can also check for both undefined AND empty (for e.g my_variable:) variable. (NOTE: only works with ansible version > 1.9, see: link)
- name: Create user
user:
name: "{{ ((my_variable == None) | ternary('default_value', my_variable)) \
if my_variable is defined else 'default_value' }}"
If anybody is looking for an option which handles nested variables, there are several such options in this github issue.
In short, you need to use "default" filter for every level of nested vars. For a variable "a.nested.var" it would look like:
- hosts: 'localhost'
tasks:
- debug:
msg: "{{ ((a | default({})).nested | default({}) ).var | default('bar') }}"
or you could set default values of empty dicts for each level of vars, maybe using "combine" filter. Or use "json_query" filter. But the option I chose seems simpler to me if you have only one level of nesting.
In case you using lookup to set default read from environment you have also set the second parameter of default to true:
- set_facts:
ansible_ssh_user: "{{ lookup('env', 'SSH_USER') | default('foo', true) }}"
You can also concatenate multiple default definitions:
- set_facts:
ansible_ssh_user: "{{ some_var.split('-')[1] | default(lookup('env','USER'), true) | default('foo') }}"
If you are assigning default value for boolean fact then ensure that no quotes is used inside default().
- name: create bool default
set_fact:
name: "{{ my_bool | default(true) }}"
For other variables used the same method given in verified answer.
- name: Create user
user:
name: "{{ my_variable | default('default_value') }}"
If you have a single play that you want to loop over the items, define that list in group_vars/all or somewhere else that makes sense:
all_items:
- first
- second
- third
- fourth
Then your task can look like this:
- name: List items or default list
debug:
var: item
with_items: "{{ varlist | default(all_items) }}"
Pass in varlist as a JSON array:
ansible-playbook <playbook_name> --extra-vars='{"varlist": [first,third]}'
Prior to that, you might also want a task that checks that each item in varlist is also in all_items:
- name: Ensure passed variables are in all_items
fail:
msg: "{{ item }} not in all_items list"
when: item not in all_items
with_items: "{{ varlist | default(all_items) }}"
The question is quite old, but what about:
- hosts: 'localhost'
tasks:
- debug:
msg: "{{ ( a | default({})).get('nested', {}).get('var','bar') }}"
It looks less cumbersome to me...
#Roman Kruglov mentioned json_query. It's perfect for nested queries.
An example of json_query sample playbook for existing and non-existing value:
- hosts: localhost
gather_facts: False
vars:
level1:
level2:
level3:
level4: "LEVEL4"
tasks:
- name: Print on existing level4
debug:
var: level1 | json_query('level2.level3.level4') # prints 'LEVEL4'
when: level1 | json_query('level2.level3.level4')
- name: Skip on inexistent level5
debug:
var: level1 | json_query('level2.level3.level4.level5') # skipped
when: level1 | json_query('level2.level3.level4.level5')
You can also use an if statement:
# Firewall manager: firewalld or ufw
firewall: "{{ 'firewalld' if ansible_os_family == 'RedHat' else 'ufw' }}"