I want to create a named pipe with ansible on Linux/Debian. In a shell script I would use the command mkfifo for creating a pipe. Now I'm wondering if there's an builtin module approach but I can't find anything in the builtin modules of ansible.
Googling the question also doesn't yield any meaningful results.
My approach would be just to execute a shell command with ansible and execute mkfifo like this for example:
- name: Create named pipe
shell: "mkfifo testpipe"
However, to be precisely, I need to check if the pipe already exists, and if yes, I need to check filetype and so on and so on...
I bet there is a convenient way but I just can't find it.
Thank you very much for your help
Edit:
I just did it this way now. I bet there are some cases I haven't catched.
- name: Check for existing pipe
shell: "test -p {{ pipe_file }}"
register: pipe_file_test
become: true
changed_when: false
- name: Delete pipe_file if its not a pipe
file:
name: "{{ pipe_file }}"
state: absent
when: pipe_file_test.rc != 0
become: true
- name: Create pipe if necessary
shell: "mkfifo {{ pipe_file }}"
when: pipe_file_test.rc != 0
become: true
command/shell modules have some crude support for idempotency:
- name: Create named pipe
command:
cmd: mkfifo /tmp/testpipe
creates: /tmp/testpipe
If /tmp/testpipe exists, ansible will report 'no changed'.
Related
I've been searching for quite some time and tried many variants and similar answers without success. Hopefully this is something simple I am missing.
Ansible 2.9.6
I am creating many playbooks that all share a large set of custom Roles for my clients. I want to keep all logic out of the playbooks, and place that logic in the roles themselves to allow maximum re-usability. Basically, I just want to add boilerplate playbooks for simple "Role" runs via tags, with some vars overrides here and there.
My problem is that I can't seem to make some roles idempotent by using conditions - the conditionals don't work. The error I get is:
fatal: [localhost]: FAILED! => {"msg": "The conditional check 'homebrew_base.rc != 0' failed.
The error was: error while evaluating conditional (homebrew_bash.rc != 0): 'homebrew_bash' is undefined
The error appears to be in '/Users/eric/code/client1/provisioning/roles/bash/tasks/main.yaml': line 12, column 3, but may
be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
- name: Change shell (chsh) for macOS to homebrew managed version
^ here
"}
Below is the boilerplate code for my playbooks:
# ./playbook.yaml
---
- name: Provisioning
hosts: localhost
connection: local
pre_tasks:
- include_vars: "{{ item }}"
with_fileglob:
- "{{ playbook_dir }}/vars/global.yaml"
tags: always
tasks:
- name: Use homebrew bash binary
include_role:
name: bash
tags: bash
The above is truncated quite a bit, but only thing missing are additional var files and a whole bunch of include_roles.
Below is my role file in the entirely though. They are largely untested because of the error I keep getting.
# ./roles/bash/tasks/main.yaml
---
- name: Check /etc/shells contains "/usr/local/bin/bash"
command: grep -Fxq "/usr/local/bin/bash" /etc/shells
register: homebrew_bash
ignore_errors: True
changed_when:
- homebrew_bash.rc != 0
- name: Check that homebrew installed /usr/local/bin/bash
stat:
path: /usr/local/bin/bash
register: homebrew_bash_binary
- name: Change shell (chsh) for macOS to homebrew managed versoin
tags: bash, chsh
shell: chsh -s /usr/local/bin/bash
become: yes
when:
- homebrew_bash.rc != 0
- homebrew_bash_binary.stat.exists = True
Ps: I do plan on abstracting those hardcoded paths into roles/bash/defaults/. But I need it working first before that.
Ps2: If there is a better way to use a contains filter (instead of the grep hack), I'm all ears.
I've tried:
making separate tasks/chsh.yaml, and using the include: command to call that task within the role. When I do this, I get an odd error telling me the variable is undefined - in the tasks/chsh.yaml - even though I am checking for the variable in tasks/main.yaml! That doesn't seem right.
using quotes in various places in the conditions
commenting out each condition: both give the same error, just differenet names.
Again, I am trying to keep this logic in the roles only - not in the playbook.
Thanks!
Figured it out. I was missing the "tags" on the conditionals!
# ./roles/bash/tasks/main.yaml
---
- name: Check /etc/shells contains "/usr/local/bin/bash"
tags: bash, chsh
command: grep -Fxq "/usr/local/bin/bash" /etc/shells
register: homebrew_bash
ignore_errors: True
changed_when:
- homebrew_bash.rc != 0
- name: Check that homebrew installed /usr/local/bin/bash
tags: bash, chsh
stat:
path: /usr/local/bin/bash
register: homebrew_bash_binary
- name: Change shell (chsh) for macOS to homebrew managed versoin
tags: bash, chsh
shell: chsh -s /usr/local/bin/bash
become: yes
when:
- homebrew_bash.rc != 0
- homebrew_bash_binary.stat.exists = True
I'm very new to Ansible
Is it possible to check if a string exists in a file using Ansible.
I want to check is a user has access to a server.
this can be done on the server using cat /etc/passwd | grep username
but I want Ansible to stop if the user is not there.
I have tried to use the lineinfile but can't seem to get it to return.
code
- name: find
lineinfile: dest=/etc/passwd
regexp=[user]
state=present
line="user"
The code above adds user to the file if he is not there. All i want to do is check. I don't want to modify the file in any way, is this possible
Thanks.
It's a tricky one. the lineinfile module is specifically intended for modifying the content of a file, but you can use it for a validation check as well.
- name: find
lineinfile:
dest: /etc/passwd
line: "user"
check_mode: yes
register: presence
failed_when: presence.changed
check_mode ensures it never updates the file.
register saves the variable as noted.
failed_when allows you to set the failure condition i.e. by adding the user because it was not found in the file.
There are multiple iterations of this that you can use based on what you want the behavior to be. lineinfile docs particular related to state and regexp should allow you to determine whether or not presence or absence is failure etc, or you can do the not presence.changed etc.
I'd probably register and evaluate a variable.
The following simple playbook works for me:
- hosts: localhost
tasks:
- name: read the passwd file
shell: cat /etc/passwd
register: user_accts
- name: a task that only happens if the user exists
when: user_accts.stdout.find('hillsy') != -1
debug: msg="user hillsy exists"
If you want to fail if there is no user:
tasks:
- shell: grep username /etc/passwd
changed_when: false
By default shell module will fail if command exit code is non zero.
So it will give you ok if username is there and fails otherwise.
I use changed_when: false to prevent changed state when grepping.
I am using the following approach, using only a grep -q and a registered variable.
Upside is that it's simple, downside is that you have a FAILED line in your output. YMMV.
- name: Check whether foobar is defined in /bar/baz
command:
cmd: 'grep -q foobar /bar/baz'
register: foobar_in_barbaz
changed_when: false
ignore_errors: true
- when: not foobar_in_barbaz.failed
name: Do something when foobar is in /bar/baz
....
- when: foobar_in_barbaz.failed
pause:
seconds: 1
content: |
You do not seem to have a foobar line in /bar/baz
If you add it, then magic stuff will happen!
I'm trying to run a python script from an ansible script. I would think this would be an easy thing to do, but I can't figure it out. I've got a project structure like this:
playbook-folder
roles
stagecode
files
mypythonscript.py
tasks
main.yml
release.yml
I'm trying to run mypythonscript.py within a task in main.yml (which is a role used in release.yml). Here's the task:
- name: run my script!
command: ./roles/stagecode/files/mypythonscript.py
args:
chdir: /dir/to/be/run/in
delegate_to: 127.0.0.1
run_once: true
I've also tried ../files/mypythonscript.py. I thought the path for ansible would be relative to the playbook, but I guess not?
I also tried debugging to figure out where I am in the middle of the script, but no luck there either.
- name: figure out where we are
stat: path=.
delegate_to: 127.0.0.1
run_once: true
register: righthere
- name: print where we are
debug: msg="{{righthere.stat.path}}"
delegate_to: 127.0.0.1
run_once: true
That just prints out ".". So helpful ...
try to use script directive, it works for me
my main.yml
---
- name: execute install script
script: get-pip.py
and get-pip.py file should be in files in the same role
If you want to be able to use a relative path to your script rather than an absolute path then you might be better using the role_path magic variable to find the path to the role and work from there.
With the structure you are using in the question the following should work:
- name: run my script!
command: ./mypythonscript.py
args:
chdir: "{{ role_path }}"/files
delegate_to: 127.0.0.1
run_once: true
An alternative/straight forward solution:
Let's say you have already built your virtual env under ./env1 and used pip3 install the needed python modules.
Now write playbook task like:
- name: Run a script using an executable in a system path
script: ./test.py
args:
executable: ./env1/bin/python
register: python_result
- name: Get stdout or stderr from the output
debug:
var: python_result.stdout
If you want to execute the inline script without having a separate script file (for example, as molecule test) you can write something like this:
- name: Test database connection
ansible.builtin.command: |
python3 -c
"
import psycopg2;
psycopg2.connect(
host='127.0.0.1',
dbname='db',
user='user',
password='password'
);
"
You can even insert Ansible variables in this string.
I'm trying to include a file only if it exists. This allows for custom "tasks/roles" between existing "tasks/roles" if needed by the user of my role. I found this:
- include: ...
when: condition
But the Ansible docs state that:
"All the tasks get evaluated, but the conditional is applied to each and every task" - http://docs.ansible.com/playbooks_conditionals.html#applying-when-to-roles-and-includes
So
- stat: path=/home/user/optional/file.yml
register: optional_file
- include: /home/user/optional/file.yml
when: optional_file.stat.exists
Will fail if the file being included doesn't exist. I guess there might be another mechanism for allowing a user to add tasks to an existing recipe. I can't let the user to add a role after mine, because they wouldn't have control of the order: their role will be executed after mine.
The with_first_found conditional can accomplish this without a stat or local_action. This conditional will go through a list of local files and execute the task with item set to the path of the first file that exists.
Including skip: true on the with_first_found options will prevent it from failing if the file does not exist.
Example:
- hosts: localhost
connection: local
gather_facts: false
tasks:
- include: "{{ item }}"
with_first_found:
- files:
- /home/user/optional/file.yml
skip: true
Thanks all for your help! I'm aswering my own question after finally trying all responses and my own question's code back in today's Ansible: ansible 2.0.1.0
My original code seems to work now, except the optional file I was looking was in my local machine, so I had to run stat through local_action and set become: no for that particular tasks, so ansible wouldn't attempt to do sudo in my local machine and error with: "sudo: a password is required\n"
- local_action: stat path=/home/user/optional/file.yml
register: optional_file
become: no
- include: /home/user/optional/file.yml
when: optional_file.stat.exists
In Ansible 2.5 and above, it can be done using tests like this:
- include: /home/user/optional/file.yml
when: "'/home/user/optional/file.yml' is file"
More details: https://docs.ansible.com/ansible/latest/user_guide/playbooks_tests.html#testing-paths
I using something similar but for the file module and what did the trick for me is to check for the variable definition, try something like:
when: optional_file.stat.exists is defined and optional_file.stat.exists
the task will run only when the variable exists.
If I am not wrong, you want to continue the playbook even the when statement false?
If so, please add this line after when:
ignore_errors: True
So your tasks will be look like this:
- stat: path=/home/user/optional/file.yml
register: optional_file
- include: /home/user/optional/file.yml
when: optional_file.stat.exists
ignore_errors: True
Please let me know, if I understand your question correctly, or can help further. Thanks
I could spend time here to bash ansible's error handling provisions, but in short, you are right and you can't use stat module for this purpose due to stated reasons.
Simplest solution for most ansible problems is to do it outside ansible. E.g.
- shell: test -f /home/user/optional/file.yml # or use -r if you're too particular.
register: optional_file
failed_when: False
- include: /home/user/optional/file.yml
when: optional_file.rc == 0
- debug: msg="/home/user/optional/file.yml did not exist and was not included"
when: optional_file.rc != 0
* failed_when added to avoid host getting excluded from further tasks when the file doesn't exist.
Using ansible-2.1.0, I'm able to use snippets like this in my playbook:
- hosts: all
gather_facts: false
tasks:
- name: Determine if local playbook exists
local_action: stat path=local.yml
register: st
- include: local.yml
when: st.stat.exists
I get no errors/failures when local.yml does not exist, and the playbook is executed (as a playbook, meaning it starts with the hosts: line, etc.)
You can do the same at the task level instead with similar code.
Using stat appears to work correctly.
There's also the option to use a Jinja2 filter for that:
- set_fact: optional_file="/home/user/optional/file.yml"
- include: ....
when: optional_file|exists
The best option I have come up with so far is this:
- include: "{{ hook_variable | default(lookup('pipe', 'pwd') ~ '/hooks/empty.yml') }}"
It's not exactly an "if-exists", but it gives users of your role the same result. Create a few variables within your role and a default empty file. The Jinja filters "default" and "lookup" take care of falling back on the empty file in case the variable was not set.
For convenience, a user could use the {{ playbook_dir }} variable for setting the paths:
hook_variable: "{{ playbook_dir }}/hooks/tasks-file.yml"
I am downloading the file with wget from ansible.
- name: Download Solr
shell: wget http://mirror.mel.bkb.net.au/pub/apache/lucene/solr/4.7.0/solr-4.7.0.zip
args:
chdir: {{project_root}}/solr
but I only want to do that if zip file does not exist in that location. Currently the system is downloading it every time.
Note: this answer covers general question of "How can i check the file existence in ansible", not a specific case of downloading file.
The problems with the previous answers using "command" or "shell" actions is that they won't work in --check mode. Actually, first action will be skipped, and next will error out on "when: solr_exists.rc != 0" condition (due to variable not being defined).
Since Ansible 1.3, there's more direct way to check for file existance - using "stat" module. It of course also works well as "local_action" to check a local file existence:
- local_action: stat path={{secrets_dir}}/secrets.yml
register: secrets_exist
- fail: msg="Production credentials not found"
when: secrets_exist.stat.exists == False
Unless you have a reason to use wget why not use get_url module. It will check if the file needs to be downloaded.
---
- hosts : all
gather_facts : no
tasks:
- get_url:
url="http://mirror.mel.bkb.net.au/pub/apache/lucene/solr/4.7.0/solr-4.7.0.zip"
dest="{{project_root}}/solr-4.7.0.zip"
NOTE: If you put the directory and not the full path in the dest ansible will still download the file to a temporary dir but do an md5 check to decide whether to copy to the dest dir.
And if you need to save state of download you can use:
---
- hosts : all
gather_facts : no
tasks:
- get_url:
url="http://mirror.mel.bkb.net.au/pub/apache/lucene/solr/4.7.0/solr-4.7.0.zip"
dest="{{project_root}}/solr-4.7.0.zip"
register: get_solr
- debug:
msg="solr was downloaded"
when: get_solr|changed
Many modules are already aware of the result and will be skipped if its already there, like file or geturl. Others like command have a creates option, which will skip this command if that file already exists (or doesn't exist, if you use the removes option).
So you should first check the available modules, if they are smart enough already. If not: I recommend the stats module. Advantage over the other solution: No "red errors but ignored" in the output.
- name: Check MySQL data directory existence
stat: path=/var/lib/mysql-slave
register: mysql_slave_data_dir
- name: Stop MySQL master to copy data directory
service: name=mysql state=stopped
sudo: yes
when: not mysql_slave_data_dir.stat.exists
There are at least two options here.
You can register a variable if the file exists, then use a when condition to execute the command on the condition that the file doesn't already exist:
- command: /usr/bin/test -e {{project_root}}/solr/solr-4.7.0.zip
register: solr_zip
ignore_errors: True
- name: Download Solr
shell: chdir={{project_root}}/solr /usr/bin/wget http://mirror.mel.bkb.net.au/pub/apache/lucene/solr/4.7.0/solr-4.7.0.zip
when: solr_zip|failed
You could also use the commands module with the creates option:
- name: Download Solr
command: /usr/bin/wget http://mirror.mel.bkb.net.au/pub/apache/lucene/solr/4.7.0/solr-4.7.0.zip chdir={{project_root}}/solr creates={{project_root}}/solr/solr-4.7.0.zip
This article might be useful
Out of it comes this example:
tasks:
- shell: if [[ -f "/etc/monitrc" ]]; then /bin/true; else /bin/false; fi
register: result
ignore_errors: True
- command: /bin/something
when: result|failed
- command: /bin/something_else
when: result|success
- command: /bin/still/something_else
when: result|skipped
So basically you can do this checking by registering a variable from a command and checking its return code. (You can also do this by checking its stdout)
- name: playbook
hosts: all
user: <your-user>
vars:
project_root: /usr/local
tasks:
- name: Check if the solr zip exists.
command: /usr/bin/test -e {{project_root}}/solr/solr-4.7.0.zip
ignore_errors: True
register: solr_exists
- name: Download Solr
shell: chdir={{project_root}}/solr wget http://mirror.mel.bkb.net.au/pub/apache/lucene/solr/4.7.0/solr-4.7.0.zip
when: solr_exists.rc != 0
This basically says that if the /usr/bin/test -e {{project_root}}/solr/solr-4.7.0.zip command returns a code that is not 0, meaning it doesn't exist then execute the task Download Solr
Hope it helps.
my favourite is to only download the file if it is newer than the local file (which includes when the local file does not exist)
the -N option with wget does this: https://www.gnu.org/software/wget/manual/html_node/Time_002dStamping-Usage.html .
sadly, i don't think there is an equivalent feature in get_url
so a very small change:
- name: Download Solr
shell: chdir={{project_root}}/solr wget -N http://<SNIPPED>/solr-4.7.0.zip
Use the creates argument
- name: Download Solr
shell: creates={{working_directory}}/solr/solr-4.7.0.zip chdir={{working_directory}}/solr wget http://mirror.mel.bkb.net.au/pub/apache/lucene/solr/4.7.0/solr-4.7.0.zip