Check authenticity of file in ansible - ansible

I have ansible role that downloads a script file, how can i check the authenticity of the file using md5sum before executing?
- name: Add xx official repository for ubuntu/debain
get_url:
url: https://script.deb.sh
dest: /opt/script.db.sh
- name: Execute the script
script: /opt/script.db.sh
i want to check the authenticity before downloading the file - can this be achieved in ansible?

If you're not using the get_url option, after the file is in the location, call the stat module using the get_checksum option as documented here.
- name: Get sha256 sum of script
stat:
path: /opt/script.db.sh
checksum_algorithm: sha256
get_checksum: yes
register: shell_stat
- name: Verify sha256sum of script before execution.
fail:
msg: "Failure, file is not correct."
when: shell_stat.stat.checksum != '19d6105fa1a581cf3ad38f67080b6d55cb152b5441ae8bdf194e593f292f31e9'
- name: Execute the script
script: /opt/script.db.sh
Update the sum on the when: line to match the file you expect.
Generating the checksum (sha256 in this example) vary on your operating system. On most Linux distributions use the sha256sum {filename} command, on OSX, use shasum -a 256 {filename}.

get_url has a checksum parameter that you could use.
- name: Add xx official repository for ubuntu/debain
get_url:
url: https://script.deb.sh
dest: /opt/script.db.sh
checksum: md5:1234
http://docs.ansible.com/ansible/latest/get_url_module.html

you can use the "checksum" parameter "get_url" module. I show you an example of a playbook that executes a "role" to download OpenJDK8 only if the md5sum is correct.
File: playbook.yml
---
- name: "Download binaries"
hosts: localhost
roles:
- openjdk
File: openjdk/tasks/main.yml
- name: "Download OpenJDK {{ openjdk_version }} binaries"
get_url:
url: https://download.java.net/openjdk/jdk8u40/ri/{{ openjdk_file }}
dest: "{{ download_destination }}"
checksum: "{{ openjdk_md5 }}"
mode: 0750
tags:
- always
File: openjdk/vars/main.yml
---
download_destination: /var/tmp
openjdk_version: "8u40-b25"
openjdk_file: "openjdk-{{ openjdk_version }}-linux-x64-10_feb_2015.tar.gz"
openjdk_md5: "md5: 4980716637f353cfb27467d57f2faf9b"
The available cryptographic algorithms in Ansible 2.7 are: sha1, sha224, sha384, sha256, sha512, md5.
It works for me, I hope for you too.

Related

Ansible - installed RPM packages for multiple servers

I am trying to query installed RPM packages on every server, and export to the JSON file.
So far I have managed to export one JSON file for each server. Server name is the JSON file name.
---
- hosts: all
become: yes
become_method: sudo
tasks:
- name: RPM packages query
shell: rpm -qa --queryformat '%{NAME} %{VERSION}\n'
register: query
- name: list report
debug:
var: query.stdout_lines
- local_action: copy content="{{ query.stdout_lines }}" dest="/home/user_name/{{ ansible_hostname }}.json"
Output, where the server name is the file name - host_name1.json:
[
"spice-glib 0.35",
"keyutils-libs-devel 1.5.8",
"mapserver 1.0",
"perl-HTTP-Cookies 6.01",
"open-vm-tools-desktop 11.0.5",
"libreport-python 2.1.11",
]
Question: how do I export the entire query.stdout_lines result from all servers into the one file, including the host name, for example:
[**host_name1_here**
]
[
"spice-glib 0.35",
"keyutils-libs-devel 1.5.8",
"mapserver 1.0",
"perl-HTTP-Cookies 6.01",
"open-vm-tools-desktop 11.0.5",
"libreport-python 2.1.11",
]
[**host_name2_here**
]
[
"spice-glib 0.35",
"keyutils-libs-devel 1.5.8",
"mapserver 1.0",
"perl-HTTP-Cookies 6.01",
]
Or something like that.
Or is there another, perhaps better way to achieve this?
- hosts: all
become: no
vars:
output_file: details.csv
tasks:
- block:
- name: RPM packages query
shell: rpm -qa --queryformat '%{NAME} %{VERSION}\n'
register: rpmdata
changed_when: false
- name: Empty existing file if already exists
copy:
dest: "{{ output_file }}"
content: 'Details'
run_once: yes
delegate_to: localhost
- name: Add details to file
lineinfile:
path: "{{ output_file }}"
line: "[{{ inventory_hostname }}]\n\
[{{ rpmdata.stdout }}]\n\n"
delegate_to: localhost
A number of changes had to be made to your original code.
Note the below:
Using shell module means running any command leaves Ansible changed status to true for that task. Given your task does not make any changes, I have made the change flag to false
The second task should be to clean an existing output file or create file if not present.
lineinfile module can be used to append to a file.
As you may have noticed the delegate_to will run 2, 3 on localhost which remain a host common to other web servers or the host machine where Ansible is being run from.

Read file on a Windows host in Ansible

Similar to the Get-Content command in PowerShell, I am looking for a way to read a file on a Windows target and save the file contents to a variable in Ansible. The documentation says that the ansible.builtin.file module is able to get file contents but this feature does not seem to be available for the win_file module.
The following pseudo-code should better explain what I'm trying to do:
- name: save file contents to variable
win_get_content:
path: C:\somefile.txt
register: file_contents
if "{{lookup('file', 'C:\somefile.txt') }}" is not functional in window, you could try:
- name: get content of file
win_shell: 'type C:\somefile.txt'
register: file_contents
- name: display content
debug:
msg: "{{ file_contents.stdout_lines }} "
You can do so using the module slurp.
It works on both windows and linux.
- name: Read file
ansible.builtin.slurp:
src: '<path/to/file>'
register: file
- name: Print file content
ansible.builtin.debug:
msg: "{{ file['content'] | b64decode }}"

Convert Ansible stat's mtime output to YYMMDD-HHMMSS

I'm trying to extract a file's last modified time in the format YYMMDD-HHMMSS (ex:210422-135018) using Ansible's stat module.
I'm getting the file's mtime in a playbook:
- name: stat
stat:
path: /path/to/file
register: file
- debug:
msg: "{{ file.stat.mtime }}"
Which returns the mtime in EPOCH format (ex: 1632916899.9357276)
I've tried following instructions in the Ansible docs, but could not get it to work.
Is there a simple way to do this via Ansible, or is it best to do it via the shell module?
Thanks!
UPDATE: Solved with help from comment below:
- name: stat
stat:
path: /path/to/file
register: file
- debug:
msg: "{{ '%Y%m%d-%H%M%S' | strftime(file.stat.mtime) }}"

ansible wget then exec scripts => get_url equivalent

I always wonder what is the good way to replace the following shell tasks using the "ansible way" (with get_url, etc.):
- name: Install oh-my-zsh
shell: wget -qO - https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh | bash -
or
- name: Install nodesource repo
shell: curl -sL https://deb.nodesource.com/setup_5.x | bash -
This worked for me:
- name: Download zsh installer
get_url:
url: https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh dest=/tmp/zsh-installer.sh
- name: Execute the zsh-installer.sh
shell: /tmp/zsh-installer.sh
- name: Remove the zsh-installer.sh
file:
path: /tmp/zsh-installer.sh
state: absent
#RaviTezu solution doesn't work because the file/script that you wish to execute must be on the machine where you execute your play/role.
As per the documentation here
The local script at path will be transferred to the remote node and then executed.
So one way to do it is by downloading the file locally and using a task like below:
- name: execute the script.sh
script: /local/path/to/script.sh
Or you can do this:
- name: download setup_5.x file to tmp dir
get_url:
url: https://deb.nodesource.com/setup_5.x
dest: /tmp/
mode: 0755
- name: execute setup_5.x script
shell: setup_5.x
args:
chdir: /tmp/
I would go for the first method if you are uploading your own script, the second method is more useful in your case because the script might gets updated in time so you are sure each time you execute it it uses the latest script.
For me, the following statement worked:
- name: "Execute Script"
shell: curl -sL https://rpm.nodesource.com/setup_6.x | bash -
Consider using the get_url or uri module rather than running curl.
For example:
- name: Download setup_8.x script
get_url: url=https://deb.nodesource.com/setup_8.x dest=/opt mode=755
- name: Setup Node.js
command: /opt/setup_8.x
- name: Install Node.js (JavaScript run-time environment)
apt: name=nodejs state=present
This playbook is what I've come up with. So far, it's as close to an idiomatic solution as I have come, and seems to be idempotent.
It will check for the existence of a command (in this case, starship) and if/when the test fails it will download the script.
---
- name: Check for Starship command
command: command -v starship >/dev/null 2>&1
register: installed
no_log: true
ignore_errors: yes
- name: Download Starship installer
get_url:
url: https://starship.rs/install.sh
dest: /tmp/starship-installer.sh
mode: 'u+rwx'
when: installed.rc != 0
register: download
- name: Run the install script
shell: /tmp/starship-installer.sh
when: download.changed
- name: Remove the starship-installer.sh
file:
path: /tmp/starship-installer.sh
state: absent
May be this basic example can help you to start:
---
- name: Installing Zsh and git
apt: pkg=zsh,git state=latest
register: installation
- name: Backing up existing ~/.zshrc
shell: if [ -f ~/.zshrc ]; then mv ~/.zshrc{,.orig}; fi
when: installation|success
sudo: no
- name: Cloning oh-my-zsh
git:
repo=https://github.com/robbyrussell/oh-my-zsh
dest=~/.oh-my-zsh
when: installation|success
register: cloning
sudo: no
- name: Creating new ~/.zshrc
copy:
src=~/.oh-my-zsh/templates/zshrc.zsh-template
dest=~/.zshrc
when: cloning|success
sudo: no
Note the: "force=yes", which will always download the script, overriding the old one.
Also note the "changed_when", which you can refine per your case.
- name: 'Download {{ helm.install_script_url }}'
environment:
http_proxy: '{{proxy_env.http_proxy | default ("") }}'
https_proxy: '{{proxy_env.https_proxy | default ("") }}'
no_proxy: '{{proxy_env.no_proxy | default ("") }}'
get_url: url={{ helm.install_script_url | default ("") }} dest=/tmp/helm_install_script force=yes mode="0755"
when: helm.install_script_url is defined
tags:
- helm_x
- name: Run {{ helm.install_script_url }}
environment:
http_proxy: '{{proxy_env.http_proxy | default ("") }}'
https_proxy: '{{proxy_env.https_proxy | default ("") }}'
no_proxy: '{{proxy_env.no_proxy | default ("") }}'
command: "/tmp/helm_install_script"
register: command_result
changed_when: "'is up-to-date' not in command_result.stdout"
when: helm.install_script_url is defined
args:
chdir: /tmp/
tags:
- helm_x

How to check if a file exists in Ansible?

I have to check whether a file exists in /etc/. If the file exists then I have to skip the task.
Here is the code I am using:
- name: checking the file exists
command: touch file.txt
when: $(! -s /etc/file.txt)
You can first check that the destination file exists or not and then make a decision based on the output of its result:
tasks:
- name: Check that the somefile.conf exists
stat:
path: /etc/file.txt
register: stat_result
- name: Create the file, if it doesnt exist already
file:
path: /etc/file.txt
state: touch
when: not stat_result.stat.exists
The stat module will do this as well as obtain a lot of other information for files. From the example documentation:
- stat: path=/path/to/something
register: p
- debug: msg="Path exists and is a directory"
when: p.stat.isdir is defined and p.stat.isdir
This can be achieved with the stat module to skip the task when file exists.
- hosts: servers
tasks:
- name: Ansible check file exists.
stat:
path: /etc/issue
register: p
- debug:
msg: "File exists..."
when: p.stat.exists
- debug:
msg: "File not found"
when: p.stat.exists == False
In general you would do this with the stat module. But the command module has the creates option which makes this very simple:
- name: touch file
command: touch /etc/file.txt
args:
creates: /etc/file.txt
I guess your touch command is just an example? Best practice would be to not check anything at all and let ansible do its job - with the correct module. So if you want to ensure the file exists you would use the file module:
- name: make sure file exists
file:
path: /etc/file.txt
state: touch
Discovered that calling stat is slow and collects a lot of info that is not required for file existence check.
After spending some time searching for solution, i discovered following solution, which works much faster:
- raw: test -e /path/to/something && echo -n true || echo -n false
register: file_exists
- debug: msg="Path exists"
when: file_exists.stdout == "true"
You can use Ansible stat module to register the file, and when module to apply the condition.
- name: Register file
stat:
path: "/tmp/test_file"
register: file_path
- name: Create file if it doesn't exists
file:
path: "/tmp/test_file"
state: touch
when: file_path.stat.exists == False
Below is the ansible play which i used to remove the file if the file exists on OS end.
- name: find out /etc/init.d/splunk file exists or not'
stat:
path: /etc/init.d/splunk
register: splunkresult
tags:
- always
- name: 'Remove splunk from init.d file if splunk already running'
file:
path: /etc/init.d/splunk
state: absent
when: splunkresult.stat.exists == true
ignore_errors: yes
tags:
- always
I have used play condition as like below
when: splunkresult.stat.exists == true --> Remove the file
you can give true/false based on your requirement
when: splunkresult.stat.exists == false
when: splunkresult.stat.exists == true
I find it can be annoying and error prone to do a lot of these .stat.exists type checks. For example they require extra care to get check mode (--check) working.
Many answers here suggest
get and register
apply when register expression is true
However, sometimes this is a code smell so always look for better ways to use Ansible, specifically there are many advantages to using the correct module. e.g.
- name: install ntpdate
package:
name: ntpdate
or
- file:
path: /etc/file.txt
owner: root
group: root
mode: 0644
But when it is not possible use one module, also investigate if you can register and check the result of a previous task. e.g.
# jmeter_version: 4.0
- name: Download Jmeter archive
get_url:
url: "http://archive.apache.org/dist/jmeter/binaries/apache-jmeter-{{ jmeter_version }}.tgz"
dest: "/opt/jmeter/apache-jmeter-{{ jmeter_version }}.tgz"
checksum: sha512:eee7d68bd1f7e7b269fabaf8f09821697165518b112a979a25c5f128c4de8ca6ad12d3b20cd9380a2b53ca52762b4c4979e564a8c2ff37196692fbd217f1e343
register: download_result
- name: Extract apache-jmeter
unarchive:
src: "/opt/jmeter/apache-jmeter-{{ jmeter_version }}.tgz"
dest: "/opt/jmeter/"
remote_src: yes
creates: "/opt/jmeter/apache-jmeter-{{ jmeter_version }}"
when: download_result.state == 'file'
Note the when: but also the creates: so --check doesn't error out
I mention this because often these less-than-ideal practices come in pairs i.e. no apt/yum package so we have to 1) download and 2) unzip
Hope this helps
I use this code and it works fine for folders and files. Just make sure there is no trailing spaces after the folder name. If folder exists , the file_exists.stdout will be "true" otherwise it will just be an empty string ""
- name: check filesystem existence
shell: if [[ -d "/folder_name" ]]; then echo "true"; fi
register: file_exists
- name: debug data
debug:
msg: "Folder exists"
when: file_exists.stdout == "true"
vars:
mypath: "/etc/file.txt"
tasks:
- name: checking the file exists
command: touch file.txt
when: mypath is not exists
A note on relative paths to complement the other answers.
When doing infrastructure as code I'm usually using roles and tasks that accept relative paths, specially for files defined in those roles.
Special variables like playbook_dir and role_path are very useful to create the absolute paths needed to test for existence.
You can use shell commands to check if file exists
- set_fact:
file: "/tmp/test_file"
- name: check file exists
shell: "ls {{ file }}"
register: file_path
ignore_errors: true
- name: create file if don't exist
shell: "touch {{ file }}"
when: file_path.rc != 0

Resources