Rendering environment variables in an Ansible shell command - ansible

I'm trying to run a shell command on a remote host using Ansible, and this command must also include a local environment variable.
While the variable is correctly set, the output is not the one I'd expect and I don't understand why.
This is a sample playbook that I prepared for this case:
---
- name: "Ansible local variable in shell command"
hosts: localhost
vars:
variable: "{{ lookup('env', 'VARIABLE') }}"
connection: local
tasks:
- name: "Prints the local variable"
shell: "echo {{ vars.variable }}"
register: "output"
- debug: var=output.stdout_lines
If running VARIABLE=random ansible-playbook playbook.yml I would expect it to print random, what I get instead is this error:
fatal: [localhost]: FAILED! => {"changed": true, "cmd": "echo {{ lookup('env', 'VARIABLE') }}", "delta": "0:00:00.004725", "end": "2023-02-17 21:56:21.282225", "msg": "non-zero return code", "rc": 2, "start": "2023-02-17 21:56:21.277500", "stderr": "/bin/sh: -c: line 1: syntax error near unexpected token `('\n/bin/sh: -c: line 1: `echo {{ lookup('env', 'VARIABLE') }}'", "stderr_lines": ["/bin/sh: -c: line 1: syntax error near unexpected token `('", "/bin/sh: -c: line 1: `echo {{ lookup('env', 'VARIABLE') }}'"], "stdout": "", "stdout_lines": []}
The most relevant part is the content of stderr, where we can see this one:
"/bin/sh: -c: line 1: syntax error near unexpected token `('\n/bin/sh: -c: line 1: `echo {{ lookup('env', 'VARIABLE') }}'
From my understanding, this means that vars.variable has been considered as a plain string instead of being rendered with the result of the lookup function.
But why does this happen? What am I missing on this one?

You must not use vars.variable (which contains the raw Jinja2 string as seen in your error message) but simply variable as demonstrated in the following fixed example:
---
- name: "Ansible local variable in shell command"
hosts: localhost
vars:
variable: "{{ lookup('env', 'VARIABLE') }}"
connection: local
tasks:
- name: "Prints the local variable"
#shell: "echo {{ vars.variable }}"
shell: "echo {{ variable }}"
register: "output"
- debug: var=output.stdout_lines
works fine for me:
$ VARIABLE="Hello, world" ansible-playbook playbook2.yml
PLAY [Ansible local variable in shell command] *******************************************************************************************************************************************************************************************************************************
TASK [Gathering Facts] *******************************************************************************************************************************************************************************************************************************************************
ok: [localhost]
TASK [Prints the local variable] *********************************************************************************************************************************************************************************************************************************************
changed: [localhost]
TASK [debug] *****************************************************************************************************************************************************************************************************************************************************************
ok: [localhost] => {
"output.stdout_lines": [
"Hello, world"
]
}
PLAY RECAP *******************************************************************************************************************************************************************************************************************************************************************
localhost : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

Related

Ansible playbook is failing for 'become:yes'

The following ansible-playbook works fine for non-sudo access.
But fails when I un-comment become:yes
---
- hosts: all
become: yes
tasks:
- name: Register the policy file in a variable
read_csv:
path: policy.csv
delegate_to: localhost
register: csv_file
- name: Check rules pre-remediation
command: "{{ item.Compliance_check }}"
register: output
with_items:
"{{ csv_file.list }}"
- name: Perform remediation
command: "{{ item.item.Remediation }}"
when: item.item.Expected_result != item.stdout
with_items:
"{{ output.results }}"
The inventory file looks like:
10.136.59.110 ansible_ssh_user=username ansible_ssh_pass=password ansible_sudo_pass=password
Error I'm facing is:
user#hostname:~/git-repo/MCI$ ansible-playbook playbook.yaml -i inventory
PLAY [all] *******************************************************************************************************************************
TASK [Gathering Facts] *******************************************************************************************************************
ok: [10.136.59.109]
TASK [Register the policy file in a variable] ********************************************************************************************
Sorry, try again.
fatal: [10.136.59.109 -> localhost]: FAILED! => {"changed": false, "module_stderr": "[sudo via ansible, key=mjcqjbcyeemygkxwycgeftiikivnylsj] password:\nsudo: no password was provided\nsudo: 1 incorrect password attempt\n", "module_stdout": "", "msg": "MODULE FAILURE\nSee stdout/stderr for the exact error", "rc": 1}
PLAY RECAP *******************************************************************************************************************************
10.136.59.109 : ok=1 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
Can't understand why this error is showing. I have tried providing correct sudo passwords in both inventory as well as at run-time using --ask-become-pass
Please help find out the cause of the error.

How to get IP address from controller's hosts file in Ansible?

I am having difficulty figuring out to set a fact in my Ansible playbook that contains the IP address of a server that is listed in the /etc/hosts file on my controller. I am running a playbook against my web server which needs the IP address of my file server. I run the command like this:
ansible-playbook deploy-webservers.yml -i inventory.ini -l webservers
My inventory file looks like this:
[fileservers]
prod-fs1.example.com
[webservers]
prod-web1.example.com
[localhost]
127.0.0.1 ansible_connection=local ansible_python_interpreter=/Users/jsmith/.virtualenvs/provision/bin/python
Here is the playbook:
---
hosts: all
gather_facts: yes
become: yes
pre_tasks:
- name: get file server's IP address
command: "grep prod-fs1 /etc/hosts | awk '{ print $1 }'"
register: fs_ip_addr
delegate_to: localhost
- debug: var={{ fs_ip_addr }}
When I run it, I get this error:
TASK [get file server's IP address] ****************************************************************************************
fatal: [prod-web1.example.com -> localhost]: FAILED! => {"changed": true, "cmd": ["grep", "prod-fs1", "/etc/hosts", "|", "awk", "{ print $0 }"], "delta": "0:00:00.010303", "end": "2020-03-03 12:24:36.207656",
"msg": "non-zero return code", "rc": 2, "start": "2020-03-03 12:24:36.197353", "stderr": "grep: |: No such file or directory\ngrep: awk: No such file or directory\ngrep: { print $0 }:
No such file or directory", "stderr_lines": ["grep: |: No such file or directory", "grep: awk: No such file or directory", "grep: { print $0 }: No such file or directory"], "stdout": "/etc/hosts:45.79.99.99 prod-fs1.example.com prod-fs1", "stdout_lines": ["/etc/hosts:45.79.99.99 prod-fs1.example.com prod-fs1"]}
PLAY RECAP ****************************************************************************************************************
prod-web1.example.com : ok=7 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0;
It looks like Ansible has a problem parsing the command when it reaches the pipe symbol. Is there a way around this problem?
Try this. No need to delegate to localhost. lookup is running always on the controller
- set_fact:
fs_ip_addr: "{{ (lookup('file', '/etc/hosts').splitlines()|
list|
select('search', search_host)|
list|
first).split().0 }}"
vars:
search_host: "prod-fs1"
The code can be simplified a bit. Note that search_host is a var.
- set_fact:
fs_ip_addr: "{{ lookup('file', '/etc/hosts').splitlines() |
select('search', search_host) |
first | split() | first }}"

How to escape double-quotes in ansible variable

I have an ansible playbook which accepts a variable, passing a variable with quotes in it(its needed), that variable will used to query against DB
Playbook
- name: Execute clear script
script: scripts/clear-documents.sh {{ids}}
Command
ansible-playbook playbooks/maintenance.yml -i hosts -t clear -e ids=["foo", "bar"]
in this process script receives the input as [foo, bar] instead of ["foo", "bar"]
I tried escaping using backslash but that did not help
ansible-playbook playbooks/maintenance.yml -i hosts -t clear -e ids=[\"foo\", \"bar\"]
Adding double quotes in playbook, makes the input like "[foo,bar]" and not ["foo", "bar"]
script: scripts/clear-documents.sh "{{ids}}"
I searched a lot but did not get any proper solution, is there a way to handler this
Note:
ansible version - 2.2.3.0
The thing you are looking for is quote, in combination with #JGK's correct usage of -e ids='["foo", "bar"]' because you were not quoting them on the way into ansible, and then you were not quoting them on the way out of ansible in that shell: task
- shell: scripts/clear-documents.sh {{ ids | quote }}
As is mentioned in How to escape backslash and double quote in Ansible (script module) you need to surround the {{ ids }} with ' ' in your shell command.
#!/usr/bin/env ansible-playbook
- hosts: localhost
gather_facts: false
become: false
tasks:
- name: Escape characters for fun and profit
vars:
string_list: '["one", "two"]'
shell: "echo '{{ string_list }}'"
register: output1
- name: Print it out
debug:
msg: "{{ output1 }}"
- name: Don't escape characters
vars:
string_list: '["one", "two"]'
shell: "echo {{ string_list }}"
register: output2
- name: Print it out
debug:
msg: "{{ output2 }}"
PLAY [localhost] ************************************************************************************************************************************************************************************************************************************************************************************************************************************************************
TASK [Escape characters for fun and profit] *********************************************************************************************************************************************************************************************************************************************************************************************************************************
changed: [localhost]
TASK [Print it out] *********************************************************************************************************************************************************************************************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": {
"changed": true,
"cmd": "echo '[\"one\", \"two\"]'",
"delta": "0:00:00.003400",
"end": "2019-05-07 12:02:32.897856",
"failed": false,
"rc": 0,
"start": "2019-05-07 12:02:32.894456",
"stderr": "",
"stderr_lines": [],
"stdout": "[\"one\", \"two\"]",
"stdout_lines": [
"[\"one\", \"two\"]"
]
}
}
TASK [Don't escape characters] **********************************************************************************************************************************************************************************************************************************************************************************************************************************************
changed: [localhost]
TASK [Print it out] *********************************************************************************************************************************************************************************************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": {
"changed": true,
"cmd": "echo [\"one\", \"two\"]",
"delta": "0:00:00.002990",
"end": "2019-05-07 12:02:33.192049",
"failed": false,
"rc": 0,
"start": "2019-05-07 12:02:33.189059",
"stderr": "",
"stderr_lines": [],
"stdout": "[one, two]",
"stdout_lines": [
"[one, two]"
]
}
}
PLAY RECAP ******************************************************************************************************************************************************************************************************************************************************************************************************************************************************************
localhost : ok=4 changed=2 unreachable=0 failed=0

Ansible: Access Shell Var in Task

What is the correct way of going about doing this: I have a task with the shell module that exports a var from the Bash shell.
Can I access said var from another task (maybe setting environment?) without having to register command output and parse shellvar_register.stdout?
Does Ansible scope an ansible_env for localhost and remote host?
---
- name: test var play
hosts: localhost
tasks:
- name: export shell var
become: no
local_action: shell export shellvar=foo
args:
executable: /bin/bash
#register: shellvar_register
- name: print debug msg
local_action:
module: debug
msg: "{{ ansible_env.shellvar }}"
% ansible-playbook playbooks/test/test_shellvar.yml
PLAY [test var play] ***
TASK [Gathering Facts] ***
ok: [localhost]
TASK [export shell var] ***
changed: [localhost -> localhost]
TASK [print debug msg] ***
fatal: [localhost]: FAILED! => {"msg": "The task includes an option
with an undefined variable. The error was: 'dict object' has no
attribute
'shellvar'\n\nThe error appears to be in '/home/robert/tmp/common.git
/playbooks/test/test_shellvar.yml': line 12, column 5, but may\nbe
elsewhere in the file depending on the exact syntax problem.\n\nThe
offending line appears to be:\n\n\n - name: print debug msg\n ^
here\n"}
to retry, use: --limit #/home/robert/tmp/common.git/playbooks
/test/test_shellvar.retry
PLAY RECAP ***
localhost: ok=2 changed=1 unreachable=0 failed=1 skipped=0
c.f. https://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.html#registering-variables
Your shell exits when the task is over. Just capture the value as an ansible value for later uses.
- name: set a var
command: 'echo foo'
register: not_a_shell_var
- name: show it
debug:
msg: "{{ not_a_shell_var.stdout }}"
you could use set_fact like in Ansible: Store command's stdout in new variable? and set a new ansible variable, which you can use in other plays too.
- hosts: loc
tasks:
- name: set a ansible variable for later use
set_fact:
new_ansible_var: "foo"
- name: run a command with this var
shell: "shellvar={{ new_ansible_var }} {{ playbook_dir }}/bash_script_using_shellvar.sh"
The first task sets the ansible_variable. The second task takes this variable for shellvar, so the bashscript can use shellvar.

How do I register a variable and persist it between plays targeted on different nodes?

I have an Ansible playbook, where I would like a variable I register in a first play targeted on one node to be available in a second play, targeted on another node.
Here is the playbook I am using:
---
- hosts: localhost
gather_facts: no
tasks:
- command: echo "hello world"
register: foo
- hosts: main
gather_facts: no
tasks:
- debug:
msg: {{ foo.stdout }}
But, when I try to access the variable in the second play, targeted on main, I get this message:
The task includes an option with an undefined variable. The error was: 'foo' is undefined
How can I access foo, registered on localhost, from main?
The problem you're running into is that you're trying to reference facts/variables of one host from those of another host.
You need to keep in mind that in Ansible, the variable foo assigned to the host localhost is distinct from the variable foo assigned to the host main or any other host.
If you want to access one hosts facts/variables from another host then you need to explicitly reference it via the hostvars variable. There's a bit more of a discussion on this in this question.
Suppose you have a playbook like this:
- hosts: localhost
gather_facts: no
tasks:
- command: echo "hello world"
register: foo
- hosts: localhost
gather_facts: no
tasks:
- debug:
var: foo
This will work because you're referencing the host localhost and localhosts's instance of the variable foo in both plays.
The output of this playbook is something like this:
PLAY [localhost] **************************************************
TASK: [command] ***************************************************
changed: [localhost]
PLAY [localhost] **************************************************
TASK: [debug] *****************************************************
ok: [localhost] => {
"var": {
"foo": {
"changed": true,
"cmd": [
"echo",
"hello world"
],
"delta": "0:00:00.004585",
"end": "2015-11-24 20:49:27.462609",
"invocation": {
"module_args": "echo \"hello world\",
"module_complex_args": {},
"module_name": "command"
},
"rc": 0,
"start": "2015-11-24 20:49:27.458024",
"stderr": "",
"stdout": "hello world",
"stdout_lines": [
"hello world"
],
"warnings": []
}
}
}
If you modify this playbook slightly to run the first play on one host and the second play on a different host, you'll get the error that you encountered.
Solution
The solution is to use Ansible's built-in hostvars variable to have the second host explicitly reference the first hosts variable.
So modify the first example like this:
- hosts: localhost
gather_facts: no
tasks:
- command: echo "hello world"
register: foo
- hosts: main
gather_facts: no
tasks:
- debug:
var: foo
when: foo is defined
- debug:
var: hostvars['localhost']['foo']
## alternatively, you can use:
# var: hostvars.localhost.foo
when: hostvars['localhost']['foo'] is defined
The output of this playbook shows that the first task is skipped because foo is not defined by the host main.
But the second task succeeds because it's explicitly referencing localhosts's instance of the variable foo:
TASK: [debug] *************************************************
skipping: [main]
TASK: [debug] *************************************************
ok: [main] => {
"var": {
"hostvars['localhost']['foo']": {
"changed": true,
"cmd": [
"echo",
"hello world"
],
"delta": "0:00:00.005950",
"end": "2015-11-24 20:54:04.319147",
"invocation": {
"module_args": "echo \"hello world\"",
"module_complex_args": {},
"module_name": "command"
},
"rc": 0,
"start": "2015-11-24 20:54:04.313197",
"stderr": "",
"stdout": "hello world",
"stdout_lines": [
"hello world"
],
"warnings": []
}
}
}
So, in a nutshell, you want to modify the variable references in your main playbook to reference the localhost variables in this manner:
{{ hostvars['localhost']['foo'] }}
{# alternatively, you can use: #}
{{ hostvars.localhost.foo }}
Use a dummy host and its variables
For example, to pass a Kubernetes token and hash from the master to the workers.
On master
- name: "Cluster token"
shell: kubeadm token list | cut -d ' ' -f1 | sed -n '2p'
register: K8S_TOKEN
- name: "CA Hash"
shell: openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | openssl rsa -pubin -outform der 2>/dev/null | openssl dgst -sha256 -hex | sed 's/^.* //'
register: K8S_MASTER_CA_HASH
- name: "Add K8S Token and Hash to dummy host"
add_host:
name: "K8S_TOKEN_HOLDER"
token: "{{ K8S_TOKEN.stdout }}"
hash: "{{ K8S_MASTER_CA_HASH.stdout }}"
- name:
debug:
msg: "[Master] K8S_TOKEN_HOLDER K8S token is {{ hostvars['K8S_TOKEN_HOLDER']['token'] }}"
- name:
debug:
msg: "[Master] K8S_TOKEN_HOLDER K8S Hash is {{ hostvars['K8S_TOKEN_HOLDER']['hash'] }}"
On worker
- name:
debug:
msg: "[Worker] K8S_TOKEN_HOLDER K8S token is {{ hostvars['K8S_TOKEN_HOLDER']['token'] }}"
- name:
debug:
msg: "[Worker] K8S_TOKEN_HOLDER K8S Hash is {{ hostvars['K8S_TOKEN_HOLDER']['hash'] }}"
- name: "Kubeadmn join"
shell: >
kubeadm join --token={{ hostvars['K8S_TOKEN_HOLDER']['token'] }}
--discovery-token-ca-cert-hash sha256:{{ hostvars['K8S_TOKEN_HOLDER']['hash'] }}
{{K8S_MASTER_NODE_IP}}:{{K8S_API_SERCURE_PORT}}
I have had similar issues with even the same host, but across different plays. The thing to remember is that facts, not variables, are the persistent things across plays. Here is how I get around the problem.
#!/usr/local/bin/ansible-playbook --inventory=./inventories/ec2.py
---
- name: "TearDown Infrastructure !!!!!!!"
hosts: localhost
gather_facts: no
vars:
aws_state: absent
vars_prompt:
- name: "aws_region"
prompt: "Enter AWS Region:"
default: 'eu-west-2'
tasks:
- name: Make vars persistant
set_fact:
aws_region: "{{aws_region}}"
aws_state: "{{aws_state}}"
- name: "TearDown Infrastructure hosts !!!!!!!"
hosts: monitoring.ec2
connection: local
gather_facts: no
tasks:
- name: set the facts per host
set_fact:
aws_region: "{{hostvars['localhost']['aws_region']}}"
aws_state: "{{hostvars['localhost']['aws_state']}}"
- debug:
msg="state {{aws_state}} region {{aws_region}} id {{ ec2_id }} "
- name: last few bits
hosts: localhost
gather_facts: no
tasks:
- debug:
msg="state {{aws_state}} region {{aws_region}} "
results in
Enter AWS Region: [eu-west-2]:
PLAY [TearDown Infrastructure !!!!!!!] ***************************************************************************************************************************************************************************************************
TASK [Make vars persistant] **************************************************************************************************************************************************************************************************************
ok: [localhost]
PLAY [TearDown Infrastructure hosts !!!!!!!] *********************************************************************************************************************************************************************************************
TASK [set the facts per host] ************************************************************************************************************************************************************************************************************
ok: [XXXXXXXXXXXXXXXXX]
TASK [debug] *****************************************************************************************************************************************************************************************************************************
ok: [XXXXXXXXXXX] => {
"changed": false,
"msg": "state absent region eu-west-2 id i-0XXXXX1 "
}
PLAY [last few bits] *********************************************************************************************************************************************************************************************************************
TASK [debug] *****************************************************************************************************************************************************************************************************************************
ok: [localhost] => {
"changed": false,
"msg": "state absent region eu-west-2 "
}
PLAY RECAP *******************************************************************************************************************************************************************************************************************************
XXXXXXXXXXXXX : ok=2 changed=0 unreachable=0 failed=0
localhost : ok=2 changed=0 unreachable=0 failed=0
You can use an Ansible known behaviour. That is using group_vars folder to load some variables at your playbook. This is intended to be used together with inventory groups, but it is still a reference to the global variable declaration. If you put a file or folder in there with the same name as the group, you want some variable to be present, Ansible will make sure it happens!
As for example, let's create a file called all and put a timestamp variable there. Then, whenever you need, you can call that variable, which will be available to every host declared on any play inside your playbook.
I usually do this to update a timestamp once at the first play and use the value to write files and folders using the same timestamp.
I'm using lineinfile module to change the line starting with timestamp :
Check if it fits for your purpose.
On your group_vars/all
timestamp: t26032021165953
On the playbook, in the first play:
hosts: localhost
gather_facts: no
- name: Set timestamp on group_vars
lineinfile:
path: "{{ playbook_dir }}/group_vars/all"
insertafter: EOF
regexp: '^timestamp:'
line: "timestamp: t{{ lookup('pipe','date +%d%m%Y%H%M%S') }}"
state: present
On the playbook, in the second play:
hosts: any_hosts
gather_facts: no
tasks:
- name: Check if timestamp is there
debug:
msg: "{{ timestamp }}"

Resources