How to determine command line options from ansible playbook - ansible

I need to determine if someone as used --ask-vault-pass option while executing their playbook or not. If they have not issued the ansible-playbook command with --ask-vault-pass, fail it.
Pass if someone run like:
ansible-playbook -i myinv.ini test.yml --ask-vault-pass
Fail if someone run like:
ansible-playbook -i myinv.ini test.yml
Note that, this is not extra-vars which I could validate.

The following is not detecting the --ask-vaul-pass option but will still achieve your requirement. Note anyway that detecting this option in itself is not sufficient for your need since you can provide the vault password in different ways (--vault-passwork-file option, environment variables...)
Basically, I'm testing one of the vars that I know is encoded and failing the playbook nicely if we cannot decode. I targeted localhost for the purpose of the test but you can target any group. This is why I used run_once on the test task to make sure we only test once for all hosts in the play. I also used no_log to make sure we don't leak any sensitive data on screen.
- hosts: localhost
gather_facts: false
vars:
test_var: !vault |
$ANSIBLE_VAULT;1.1;AES256
<encrypted data goes here>
pre_tasks:
- block:
- name: verify we can decode vault
debug:
var: test_var
no_log: true
run_once: true
rescue:
- name: fail playbook if decode failed
fail:
msg: You did not provide a vault pass or it is not recognized
tasks:
- name: a task
debug:
msg: I'll be played if pre_tasks are ok
Would this make the trick in your situation ?

Related

Passing hostname to ansible playbook through extravars

I have to pass the host on which the Ansible command will be executed through extra vars.
I don't know in advance to which hosts the tasks will be applied to, and, therefore, my inventory file is currently missing the hosts: variable.
If I understood from the article "How to pass extra variables to an Ansible playbook" correctly, overwriting hosts is only possible by having already composed groups of hosts.
From the post Ansible issuing warning about localhost I gathered that referencing hosts to be managed in an Ansible inventory is a must, however, I still have doubts about it since the usage of extra vars was not mentioned in the given question.
So my question is: What can i do in order to make this playbook work?
- hosts: "{{ host }}"
tasks:
- name: KLIST COMMAND
command: klist
register: klist_result
- name: TEST COMMAND
ansible.builtin.shell: echo hi > /tmp/test_result.txt
... referencing hosts to be managed in an Ansible inventory is a must
Yes, that's the case. Regarding your question
What can I do in order to make this playbook work? (annot. without a "valid" inventory file)
you could try with the following workaround.
---
- hosts: localhost
become: false
gather_facts: false
tasks:
- add_host:
hostname: "{{ target_hosts }}"
group: dynamic
- hosts: dynamic
become: true
gather_facts: true
tasks:
- name: Show hostname
shell:
cmd: "hostname && who am i"
register: result
- name: Show result
debug:
var: result
A call with
ansible-playbook hosts.yml --extra-vars="target_hosts=test.example.com"
resulting into execution on
TASK [add_host] ***********
changed: [localhost]
PLAY [dynamic] ************
TASK [Show hostname] ******
changed: [test.example.com]
In any case it is recommended to check how to build your inventory.
Further Documentation
add_host module – Add a host (and alternatively a group) to the ansible-playbook in-memory inventory

How to obfuscate strings in a playbook

I have a playbook which calls a script file. It is going to pass a secret to the script file.
What I would like is for the secret not to be shown in standard out.
According to the ansible documentation, no_log: true can be used however this will not work when used in a ANSIBLE_DEBUG env variable is set true.
What I am also finding is that in the task "Set Execution File and parameters", the variables are being displayed even with no_log: true.
---
- block:
- name: Set Execution File and parameters
set_fact:
scriptfile: "{{ansible_user_dir}}\\scripts\\host_check.ps1"
params: " -servername '{{the_host_name}}' -secret {{my_secret}}"
- name: Execute script
win_command: powershell.exe "{{scriptfile}}" "{{params}}"
no_log: True
rescue:
- debug:
msg: "Play failed"
I would like to replace the my_secret variable with *******
Actually no_log: true is all you can do. Ansible will need to know your secret to use it and anybody who can set ANSIBLE_DEBUG can also just edit your playbook to display the secret. So as much as you hide it, it can always be revealed.
You have, however, the possibility to encrypt the secret using ansible-vault and pass the password to ansible, so nobody except you (or has the password) can use (and my this display) the secret.
This is how you encrypt a value (<secret-value> in this case):
ansible-vault encrypt_string --ask-vault-pass '<secret-value>' --name 'secret'
You will be asked for a password (I used test here) and then will get something that looks like this:
secret: !vault |
$ANSIBLE_VAULT;1.1;AES256
36323534396462626132653332653266393832306337336164373834626330373132363136633865
6161623130363364643138633236306334313833663535380a666261383838313631396264626534
66373637653937616231353361633635353238333630303563343630333161626137396332616263
3966356531613234370a633331333862616164386130663262613430316630373230373833313330
6265
You place that in your inventory like this (just an example what it should look like, you need to put in your hosts and other variables):
---
all:
hosts:
your-host:
vars:
secret: !vault |
$ANSIBLE_VAULT;1.1;AES256
36323534396462626132653332653266393832306337336164373834626330373132363136633865
6161623130363364643138633236306334313833663535380a666261383838313631396264626534
66373637653937616231353361633635353238333630303563343630333161626137396332616263
3966356531613234370a633331333862616164386130663262613430316630373230373833313330
6265
the_host_name: host.name
ansible_user_dir: 'C:\\some\\dir'
Then you can do this in your playbook:
---
- block:
- name: Execute script
win_command: 'powershell.exe "{{ ansible_user_dir }}\\scripts\\host_check.ps1" -servername "{{ the_host_name }}" -secret "{{ my_secret }}"'
no_log: true
rescue:
- debug:
msg: "Play failed"
You need to run it with --ask-vault-pass like this:
ansible-playbook -i your-inventory.yml your-playbook.yml --ask-vault-pass
It will ask you for the password every time you run it, so make sure to keep it somewhere.
If you are using multiple encrypted values in one run, you need to use the same password to encrypt all of them.

Ansible force to use --limit and --tags when run playbook

I am using ansible 2.9.4. My goal is to deny run some playbook on all nodes by accident or without tags. This is my app.yaml:
- hosts: all
remote_user: root
vars:
server_domain: mydomain.com
project_name: project
tasks:
- name: checking limit arg
fail:
msg: "you must use -l or --limit - when you really want to use all hosts, use -l 'all'"
when: ansible_limit is not defined
run_once: true
- name: "suppress message if tag given"
set_fact: suppress_message=yes
tags: dev,test,prod
- name: "message"
fail:
msg: "You didn't choose environment 'dev,test,prod'"
when: suppress_message is not defined
roles:
- testrole
The problem is, when I am not use --limit option, the role testrole run successfully and then the error message occurs - too late if I already run it on all nodes.
Even when I specify tags --tags "mytag" it will not check if limit was specified.
By similar way I would like to force to use tags, so everytime when you run playbook, you should specify environment tag (dev, test, prod) - e.g ssh keys for different environments, configuration files, etc...
What I would expect from this, that If I not specified tag dev, test or prod, the suppress_message would not be specified so next task with name message would fail with message "You didn't choose environement".
The fact is, If I not specified any tag:
- supress message have state OK
- message is skipped
If I specify valid tag --tags "dev":
- supress message have state OK
- message is not even mentioned (I would expect skipping)
If I specify "invalid tag" --tags "dev123":
- supress message is not mentioned
- message is not mentioned
The solution for limit could be replace - hosts: all with - hosts: randomtext so when no limit is specified there will be no match but what about tags/environments? I am quiet lost about how ansible works. The logic about this decisions what will run is quiet chaotic from this example.
Below is an example playbook that should achieve what you need to do.
Hosts are defined in a myhosts variable on the command line, the first task will abort the play if this variable is not set
Through use of the two “special tags”, always and never, we can ensure that:
the above check always runs, and
the inclusion of your testrole never runs — unless dev, test, or prod tags are explicitly specified
There’s a helpful message before the inclusion of the testrole, so the user is not left confused if the play exits silently because of unset tags
- hosts: '{{ myhosts | default("localhost") }}'
tasks:
- name: Fail if hosts are not defined
run_once: true
fail:
msg: >
You must define hosts in the myhosts variable,
e.g. `-e myhosts=foo.example.com` on the command line
when: myhosts is undefined
tags:
- always
- name: Helpful message
run_once: true
debug:
msg: >
This playbook does nothing unless the environment is specified with
the `--tags` option on the command line (dev, test, or prod).
tags:
- always
- name: Include role only when tags are specified
include_role:
name: testrole
tags:
- never
- dev
- test
- prod
This would be then executed like so:
$ ansible-playbook app.yaml --extra-vars myhosts=foo.example.com --tags dev
Change tasks to pre_tasks.
The order is pre_tasks, roles, tasks, post_tasks.
https://docs.ansible.com/ansible/latest/user_guide/playbooks_reuse_roles.html
Playbook ordering is irrelevant.
Another option is add a task to include the role. So change
roles:
- testrole
to
- include_role:
name: testrole
Try using tags like this -
tags:
- dev,test,prod

ERROR! no action detected in task. This often indicates a misspelled module name, or incorrect module path

I trying to create a simple paybook with a common role. Unfortunately I get stymied by ansible. I have looked up and down the internet for solution for this error.
The setup:
I am running ansible 2.7.4 on Ubuntu 18.04
directory structure:
~/Ansible_Do
playbook.yml
inventory (hosts file)
/roles
/common
/defaults
main.yml (other variables)
/tasks
main.yml
richard_e.yml
/vars
vars_and_stuff.yml (vault)
I have a simple playbook.yml
---
# My playbook 1
- hosts: test
- name: Go to common role to run tasks.
roles:
- common
tasks:
- name: echo something
shell: echo $(ip addr | grep inet)
...
I run this command to start the playbook:
~/Ansible_Do$ ansible-playbook -vv --vault-id #prompt -i ~/Ansible_Do/inventory playbook.yml
I enter the vault password continuing the playbook.
The playbook starts pulls facts from the test group of servers. Then reads the role and works to /roles/common. That calls the /common/tasks/main.yml file. This is where the error happens.
The error appears to have been in '/home/~/Ansible_Do/roles/common/tasks/main.yml': line 8, column 3
# Common/tasks file
---
- name: Bring variable from vault
include_vars:
file: vars_and_stuff.yml
name: My_password
- name: Super Richard <====== Error
become: yes
vars:
ansible_become_pass: "{{ My_password }}"
- import_tasks: ./roles/common/tasks/ricahrd_e.yml
...
The ./roles/common/tasks/ricahrd_e.yml is a simple testing task.
---
- name: say hi
debug:
msg: "Active server."
...
The error on "- name". I have checked online and in the Ansible docs to see if there is a key I'm missing. I found an example for include_vars in a /role/tasks (https://gist.github.com/halberom/ef3ea6d6764e929923b0888740e05211) showing proper syntax (I presume) in a simple role. The code works as parts, but not together.
I have reached what I can understand. I feel that is error is utterly simple and I am missing something (forest for the trees).
The error means exactly what it says, except the "module name" is not misspelled in your case, but missing altogether.
This...
- name: Super Richard <====== Error
become: yes
vars:
ansible_become_pass: "{{ My_password }}"
... is not a valid task definition, it does not declare an action.
An action in Ansible is a call to a module, hence "misspelled module name".
The error comes after name, because that's where Ansible expects the name of the "module" that you want to call, e.g. shell in your first example.
You are probably assuming that become is a "module", but it is not.
It is a "playbook keyword", in this case applied on the task level, which has the effect that you become another user for this task only.
But as the task has no action, you get this error.
See docs:
Playbook keywords
Understanding privilege escalation
After a bit of work I got the playbook to work. Knowing that 'become' is not a task was the start. I also found out how to pull the proper vars from the vault.
# My first playbook 1
- hosts: test
become: yes
vars_files:
- ./roles/common/vars/vars_and_stuff.yml
vars:
ansible_become_pass: "{{ My_password }}"
roles:
- common
tasks:
- name: echo something
shell: echo $(ip addr | grep inet)
The vars file access the vault and then vars: pulls the password used by become. With become in force I ran the other tasks in the common role with a last standalone task. Lastly, don't try to - name: at the top level of the playbook as it trigger a hosts undefined error.

Override hosts variable of Ansible playbook from the command line

This is a fragment of a playbook that I'm using (server.yml):
- name: Determine Remote User
hosts: web
gather_facts: false
roles:
- { role: remote-user, tags: [remote-user, always] }
My hosts file has different groups of servers, e.g.
[web]
x.x.x.x
[droplets]
x.x.x.x
Now I want to execute ansible-playbook -i hosts/<env> server.yml and override hosts: web from server.yml to run this playbook for [droplets].
Can I just override as a one time off thing, without editing server.yml directly?
Thanks.
I don't think Ansible provides this feature, which it should. Here's something that you can do:
hosts: "{{ variable_host | default('web') }}"
and you can pass variable_host from either command-line or from a vars file, e.g.:
ansible-playbook server.yml --extra-vars "variable_host=newtarget(s)"
For anyone who might come looking for the solution.
Play Book
- hosts: '{{ host }}'
tasks:
- debug: msg="Host is {{ ansible_fqdn }}"
Inventory
[web]
x.x.x.x
[droplets]
x.x.x.x
Command: ansible-playbook deplyment.yml -i hosts --extra-vars "host=droplets"
So you can specify the group name in the extra-vars
We use a simple fail task to force the user to specify the Ansible limit option, so that we don't execute on all hosts by default/accident.
The easiest way I found is this:
---
- name: Force limit
# 'all' is okay here, because the fail task will force the user to specify a limit on the command line, using -l or --limit
hosts: 'all'
tasks:
- name: checking limit arg
fail:
msg: "you must use -l or --limit - when you really want to use all hosts, use -l 'all'"
when: ansible_limit is not defined
run_once: true
Now we must use the -l (= --limit option) when we run the playbook, e.g.
ansible-playbook playbook.yml -l www.example.com
Limit option docs:
Limit to one or more hosts This is required when one wants to run a
playbook against a host group, but only against one or more members of
that group.
Limit to one host
ansible-playbook playbooks/PLAYBOOK_NAME.yml --limit "host1"
Limit to multiple hosts
ansible-playbook playbooks/PLAYBOOK_NAME.yml --limit "host1,host2"
Negated limit.
NOTE: Single quotes MUST be used to prevent bash
interpolation.
ansible-playbook playbooks/PLAYBOOK_NAME.yml --limit 'all:!host1'
Limit to host group
ansible-playbook playbooks/PLAYBOOK_NAME.yml --limit 'group1'
This is a bit late, but I think you could use the --limit or -l command to limit the pattern to more specific hosts. (version 2.3.2.0)
You could have
- hosts: all (or group)
tasks:
- some_task
and then ansible-playbook playbook.yml -l some_more_strict_host_or_pattern
and use the --list-hosts flag to see on which hosts this configuration would be applied.
An other solution is to use the special variable ansible_limit which is the contents of the --limit CLI option for the current execution of Ansible.
- hosts: "{{ ansible_limit | default(omit) }}"
If the --limit option is omitted, then Ansible issues a warning, but does nothing since no host matched.
[WARNING]: Could not match supplied host pattern, ignoring: None
PLAY ****************************************************************
skipping: no hosts matched
I'm using another approach that doesn't need any inventory and works with this simple command:
ansible-playbook site.yml -e working_host=myhost
To perform that, you need a playbook with two plays:
first play runs on localhost and add a host (from given variable) in a known group in inmemory inventory
second play runs on this known group
A working example (copy it and runs it with previous command):
- hosts: localhost
connection: local
tasks:
- add_host:
name: "{{ working_host }}"
groups: working_group
changed_when: false
- hosts: working_group
gather_facts: false
tasks:
- debug:
msg: "I'm on {{ ansible_host }}"
I'm using ansible 2.4.3 and 2.3.3
I changed mine to default to no host and have a check to catch it. That way the user or cron is forced to provide a single host or group etc. I like the logic from the comment from #wallydrag. The empty_group contains no hosts in the inventory.
- hosts: "{{ variable_host | default('empty_group') }}"
Then add the check in tasks:
tasks:
- name: Fail script if required variable_host parameter is missing
fail:
msg: "You have to add the --extra-vars='variable_host='"
when: (variable_host is not defined) or (variable_host == "")
Just came across this googling for a solution. Actually, there is one in Ansible 2.5. You can specify your inventory file with --inventory, like this: ansible --inventory configs/hosts --list-hosts all
If you want to run a task that's associated with a host, but on different host, you should try delegate_to.
In your case, you should delegate to your localhost (ansible master) and calling ansible-playbook command
I am using ansible 2.5 (2.5.3 exactly), and it seems that the vars file is loaded before the hosts param is executed. So you can set the host in a vars.yml file and just write hosts: {{ host_var }} in your playbook
For example, in my playbook.yml:
---
- hosts: "{{ host_name }}"
become: yes
vars_files:
- vars/project.yml
tasks:
...
And inside vars/project.yml:
---
# general
host_name: your-fancy-host-name
Here's a cool solution I came up to safely specify hosts via the --limit option. In this example, the play will end if the playbook was executed without any hosts specified via the --limit option.
This was tested on Ansible version 2.7.10
---
- name: Playbook will fail if hosts not specified via --limit option.
# Hosts must be set via limit.
hosts: "{{ play_hosts }}"
connection: local
gather_facts: false
tasks:
- set_fact:
inventory_hosts: []
- set_fact:
inventory_hosts: "{{inventory_hosts + [item]}}"
with_items: "{{hostvars.keys()|list}}"
- meta: end_play
when: "(play_hosts|length) == (inventory_hosts|length)"
- debug:
msg: "About to execute tasks/roles for {{inventory_hostname}}"
This worked for me as I am using Azure devops to deploy an application using CICD pipelines. I had to make this hosts (in yml file) more dynamic so in release pipeline I can add it's value, for example:
--extra-vars "host=$(target_host)"
pipeline_variable
My ansible playbook looks like this
- name: Apply configuration to test nodes
hosts: '{{ host }}'

Resources