Ansible copy doesn't set file mode correctly - file

I've got an Ansible script which among many things copy's some files to the server:
- name: copy vhost basic files to folder
copy:
src: "{{ item }}"
dest: /var/www/vhosts/mmpew/
mode: 664
owner: "{{ deploy_user }}"
group: "{{ deploy_user }}"
with_fileglob:
- ../files/vhost/*
Locally on my Macbook the files have the permissions -rw-r--r--, but even though I set the mode in the ansible script to 664, the resulting files on the server have the permissions -r-----rwt.
Why oh why do the resulting files on the server not match either the mode set in the ansible script, or the original permissions from my local filesystem from which they are copied?
I even tried to set the mode correctly using the Ansible file module:
- name: Make sure the files I just uploaded are chmodded correctly
file:
path: /var/www/vhosts/mmpew/{{ item }}
mode: 644
with_items:
- the.txt
- files.php
- here.py
but even though I get no errors from Ansible, the file modes are not set correctly.
Could anybody enlighten me as to what is wrong here? All tips are welcome!

Use mode: 0644
The 0 is necessary.

You can specify the mode symbolically:
mode: u=rw,g=r,o=r
This is more readable and less error-prone. Symbolic mode is supported by Ansible >= 1.8, according to the documentation.

there are two kinds of method to define the mode
first:
mode: 0644
second:
mode: '644'

Related

How I debug content in many file using Ansible

I want to use content in .txt file and I tried to debug content using following code.
- name: Find .txt files
find:
paths: "{{output_path}}"
patterns: '*.txt,'
register: file_path
- name: Show content
debug:
msg: "{{lookup('file', item.path)}}"
with_items: "{{file_path.files}}"
But I got this error.
[WARNING]: Unable to find '/path/file.txt' in expected paths (use -vvvvv to see paths)
TASK [Show content] ******************************************************
fatal: [10.0.2.40]: FAILED! => {"msg": "An unhandled exception occurred
while running the lookup plugin 'file'. Error was a <class 'ansible.errors.AnsibleError'>,
original message: could not locate file in lookup: /path/file.txt"}
How I fix this error?
There's nothing inherently wrong with your playbook, except that you have a comma inside the find pattern (this might be just a typo, but you should check it out) if you are running this playbook locally. In the case that you are running this playbook on a remote server, you should try to use a different module like slurp or fetch.
slurp works great if you need to keep the contents of the txt in memory to use it on another task. Bear in mind that ansible will encode the slurp module's output in base64, so you should decode it first when you want to use it. From the module example page:.`
- name: Find out what the remote machine's mounts are
ansible.builtin.slurp:
src: /proc/mounts
register: mounts
- name: Print returned information
ansible.builtin.debug:
msg: "{{ mounts['content'] | b64decode }}"
You can verify what I am saying with the following example:
I tried replicating your situation locally. On a temporary folder, I run the following command to populate it with many .txt files:
echo {001..099} > file_{001..099}.txt
Then I wrote the same playbook that you provided:
#
# show_contents.yml
#
- hosts: localhost
gather_facts: no
tasks:
- name: Find .txt files
find:
paths: "{{ output_files }}"
patterns: "*.txt"
register: file_path
- name: Debug the file_path variable
debug:
var: file_path
- name: Get the contents of the files using debug
debug:
msg: "{{ lookup('file', item.path ) }}"
loop: "{{ file_path.files }}"
If you run this playbook, passing the appropriate output_files variable with --extra-vars, the playbook works fine.
ansible-playbook show_contents.yml --extra-vars "output_files=/tmp/ansible"
You'll see that the playbook runs without an issue. Try using this example to figure out what you are trying to achieve. And then modify the playbook to use some of the previously mentioned modules when working with remote servers.

ansible loop over file list and check file exists, if not download it

Not sure how to have this logic implemented, I know how to do it a single file :
- name: Obtain information about a file
win_stat:
path: "C:\myfile.txt"
register: fileinfo
- [...]
when: fileinfo.exists == False
how should I go with a list of files?
If you just want to reduce the steps for doing this, you should be able to do your download step (not shown in your example) with ignore_errors: yes on your download commands. If you use a combination of ignore_errors: yes and register, you can even tell whether the command failed.
If you're looking to make it a bit more efficient, you can do the stat in a single task and then examine the results of that. When you execute a task with a list, you get a hash of answers.
Assuming you have a list of file names/paths in ssh_key_config, you use the stat and then you can loop over the items (which conveniently have the file name in them).
- name: Check to see if file exists
stat:
path: "{{ remote_dir }}/{{ item }}"
register: stat_results
with_items: "{{ target_files }}"
ignore_errors: True
- name: perform operation
fetch:
src: "{{ remote_dir }}/{{ item.item }}"
dest: "{{ your_dest_dir }}"
flat: yes
with_items: "{{ stat_results.results }}"
when: item.stat.exists == False
In this case, the assumptions are that remote_dir contains the remote directory on the host, target_files contains the actual file names, and your_dest_dir contains the location you want the files placed locally.
I don't do much with Windows and Ansible, but win_stat is documented pretty much the same as stat, so you can likely just replace that.
Also note that this expects the list of files, not a glob. If you use a glob (for example, you want to retrieve all files with a certain extension from the remote), then you would not use the with_items clause, and you'd need to use the item.stat.filename and/or item.stat.path to retrieve the file remotely (since the item.item would contain the request item, which would be the glob.

Ansible looping through files

Prior to Ansible 2.5, the syntax for loops used to be with_x. Starting at 2.5, loop is favored and with_x basically disappeared from the docs.
Still, the docs mention exemples of how to replace with_x with loop. But I'm clueless as to how we're now supposed to loop through a directory of files.
Let's say I need to upload all the files within a given dir, I used to use with_fileglob.
- name: Install local checks
copy:
src: "{{ item }}"
dest: /etc/sensu/plugins/
owner: sensu
group: sensu
mode: 0744
with_fileglob:
- plugins/*
So what's the modern equivalent? Is it even possible? I know I still can use with_fileglob but as I'm writing new roles, I'd better have them future-proof.
The equivalent is
loop: "{{ lookup('fileglob', 'plugins/*', wantlist=True) }}"
Here is the doc.
From the current Ansible loops doc:
Any with_* statement that requires using lookup within a loop should
not be converted to use the loop keyword. For example, instead of
doing:
loop: "{{ lookup('fileglob', '*.txt', wantlist=True) }}"
it’s cleaner to keep:
with_fileglob: '*.txt'

Is there an elegant way to check file integrity with md5 in ansible using md5 files fetched from server?

I have several files on a server that I need to download from an ansible playbook, but because the connection has good chances of interruption I would like to check their integrity after download.
I'm considering two approaches:
Store the md5 of those files in ansible as vars
Store the md5 of those files on the server as files with the extension .md5. Such a pair would look like: file.extension and file.extension.md5.
The first approach introduces overhead in maintaining the md5s in ansible. So everytime someone adds a new file, he needs to make sure he adds the md5 in the right place.
But as an advantage, there is a solution for this, using the built in check from get_url action in conjunction with checksum=md5. E.g.:
action: get_url: url=http://example.com/path/file.conf dest=/etc/foo.conf checksum=md5:66dffb5228a211e61d6d7ef4a86f5758
The second approach is more elegant and the narrows the responsibility. When someone adds a new file on the server, he will make sure to add the .md5 as well and won't even need to use the ansible playbooks.
Is there a way to use the checksum approach to match the md5 from a file?
If you wish to go with your method of storing the checksum in files on the server, you can definitely use the get_url checksum arg to validate it.
Download the .md5 file and read it into a var:
- set_fact:
md5_value: "{{ lookup('file', '/etc/myfile.md5') }}"
And then when you download the file, pass the contents of md5_value to get_url:
- get_url:
url: http://example.com
dest: /my/dest/file
checksum: "md5:{{ md5_value }}"
force: true
Note that it is vital to specify a path to a file in dest; if you set this to a directory (and have a filename in url), the behavior changes significantly.
Note also that you probably need the force: true. This will cause a new file to download every time you run it. The checksum is only triggered when files are downloaded. If the file already exists on your host it won't bother to validate the sum of the existing file, which might not be desirable.
To avoid the download every time you could stat to see if the file already exists, see what its sum is, and set the force param conditionally.
- stat:
path: /my/dest/file
register: existing_file
- set_fact:
force_new_download: "{{ existing_file.stat.md5 != md5_value }}"
when: existing_file.stat.exists
- get_url:
url: http://example.com
dest: /my/dest/file
checksum: "md5:{{ md5_value }}"
force: "{{ force_new_download | default ('false') }}"
Also, if you are pulling the sums/artifacts from some sort of web server you can actually get the value of the sum right from the url without having to actually download the file to the host. Here is an example using a Nexus server that would host the artifacts and their sums:
- set_fact:
md5_value: "{{ item }}"
with_url: http://my_nexus_server.com:8081/nexus/service/local/artifact/maven/content?g=log4j&a=log4j&v=1.2.9&r=central&e=jar.md5
This could be used in place of using get_url to download the md5 file and then using lookup to read from it.
With the stat module:
- stat:
path: "path/to/your/file"
register: your_file_info
- debug:
var: your_file_info.stat.md5
The elegant solution will be using the below 3 modules provided by ansible itself
http://docs.ansible.com/ansible/stat_module.html
use the stat module to extract the md5 value and register it in a variable
http://docs.ansible.com/ansible/copy_module.html
while using the copy module to copy the file from the server, register the return value of md5 in another variable
http://docs.ansible.com/ansible/playbooks_conditionals.html
use this conditional module to compare the above 2 variables and print the results whether the file is copied properly or not
Another solution is to use url lookup (tested on ansible-2.3.1.0):
- name: Download
get_url:
url: "http://localhost/file"
dest: "/tmp/file"
checksum: "md5:{{ lookup('url', 'http://localhost/file.md5') }}"
Wrote an ansible module with the help of https://pypi.org/project/checksumdir
The module can be found here
Example:
- get_checksum:
path: path/to/directory
checksum_type: sha1/md5/sha256/sha512
register: checksum

How to create an empty file with Ansible?

What is the easiest way to create an empty file using Ansible? I know I can save an empty file into the files directory and then copy it to the remote host, but I find that somewhat unsatisfactory.
Another way is to touch a file on the remote host:
- name: create fake 'nologin' shell
file: path=/etc/nologin state=touch owner=root group=sys mode=0555
But then the file gets touched every time, showing up as a yellow line in the log, which is also unsatisfactory...
Is there any better solution to this simple problem?
The documentation of the file module says:
If state=file, the file will NOT be created if it does not exist, see the copy or template module if you want that behavior.
So we use the copy module, using force: false to create a new empty file only when the file does not yet exist (if the file exists, its content is preserved).
- name: ensure file exists
copy:
content: ""
dest: /etc/nologin
force: false
group: sys
owner: root
mode: 0555
This is a declarative and elegant solution.
Something like this (using the stat module first to gather data about it and then filtering using a conditional) should work:
- stat:
path: /etc/nologin
register: p
- name: create fake 'nologin' shell
file:
path: /etc/nologin
state: touch
owner: root
group: sys
mode: 0555
when: p.stat.exists is defined and not p.stat.exists
You might alternatively be able to leverage the changed_when functionality.
Another option, using the command module:
- name: touch file only when it does not exists
command: touch /path/to/file
args:
creates: /path/to/file
The creates argument ensures that this action is not performed if the file exists.
With ansible 2.7+ only
Ansible file module provide a way to touch file without modifying its time.
- name: touch a file, but do not change access time, making this task idempotent
file:
path: /etc/foo.conf
state: touch
mode: u+rw,g-wx,o-rwx
modification_time: preserve
access_time: preserve
Reference:
https://docs.ansible.com/ansible/latest/modules/file_module.html
Building on the accepted answer, if you want the file to be checked for permissions on every run, and these changed accordingly if the file exists, or just create the file if it doesn't exist, you can use the following:
- stat: path=/etc/nologin
register: p
- name: create fake 'nologin' shell
file: path=/etc/nologin
owner=root
group=sys
mode=0555
state={{ "file" if p.stat.exists else "touch"}}
file: path=/etc/nologin state=touch
Full equivalent of touch (new in 1.4+) - use stat if you don't want to change file timestamp.
Turns out I don't have enough reputation to put this as a comment, which would be a more appropriate place for this:
Re. AllBlackt's answer, if you prefer Ansible's multiline format you need to adjust the quoting for state (I spent a few minutes working this out, so hopefully this speeds someone else up),
- stat:
path: "/etc/nologin"
register: p
- name: create fake 'nologin' shell
file:
path: "/etc/nologin"
owner: root
group: sys
mode: 0555
state: '{{ "file" if p.stat.exists else "touch" }}'
Changed if file not exists. Create empty file.
- name: create fake 'nologin' shell
file:
path: /etc/nologin
state: touch
register: p
changed_when: p.diff.before.state == "absent"
A combination of two answers, with a twist. The code will be detected as changed, when the file is created or the permission updated.
- name: Touch again the same file, but dont change times this makes the task idempotent
file:
path: /etc/foo.conf
state: touch
mode: 0644
modification_time: preserve
access_time: preserve
changed_when: >
p.diff.before.state == "absent" or
p.diff.before.mode|default("0644") != "0644"
and a version that also corrects the owner and group and detects it as changed when it does correct these:
- name: Touch again the same file, but dont change times this makes the task idempotent
file:
path: /etc/foo.conf
state: touch
state: touch
mode: 0644
owner: root
group: root
modification_time: preserve
access_time: preserve
register: p
changed_when: >
p.diff.before.state == "absent" or
p.diff.before.mode|default("0644") != "0644" or
p.diff.before.owner|default(0) != 0 or
p.diff.before.group|default(0) != 0
In order to create a file in the remote machine with the ad-hoc command
ansible client -m file -a"dest=/tmp/file state=touch"
Please correct me if I am wrong

Resources