Ansible register and .changed not working - ansible

I have a playbook that installs a package, and needs to run a command if the package is installed. I used register <variable> and <variable>.changed to do this, however, this doesn't seem to work in Ansible 2.0 unless I'm doing it wrong. Here's my code.
- name: install syncthing (arch)
pacman: name=syncthing state=latest
when: ansible_distribution in ['Archlinux', 'Manjaro Linux']
register: syncthing
- name: enable syncthing
command: systemctl enable syncthing#jay.service
when: syncthing.changed
What happens is the first block installs the syncthing package, and gives me the following output:
changed: [myhost]
Then the next block should execute since the previous step registered a change, but unfortunately, it doesn't:
skipping: [10.10.99.193]
I'm hoping that there's an easy solution to this, I seem to be doing this correctly from what I've read of the documentation, as well as the following post: https://raymii.org/s/tutorials/Ansible_-_Only-do-something-if-another-action-changed.html

- name: install syncthing (arch)
pacman: name=syncthing state=latest
when: ansible_distribution in ['Archlinux', 'Manjaro Linux']
register: syncthing
- name: enable syncthing
command: systemctl enable syncthing#jay.service
when: ansible_distribution in ['Archlinux', 'Manjaro Linux'] and syncthing is defined and syncthing.changed

Related

Ansible: Playbook to install packages using yum and dnf

I'm having problem executing an Ansible playbook to install a package using yum on RHEL7 and dnf on RHEL8.
I'm using a condition as shown on my playbook below, but keep getting errors.
Error
{"msg": "The conditional check 'ansible_os_family == \"RedHat\" and ansible_lsb.major_release|int == \"7\"' failed. The error was: error while evaluating conditional (ansible_os_family == \"RedHat\" and ansible_lsb.major_release|int == \"7\"): 'dict object' has no attribute 'major_release'\n\nThe error appears to be in '/ansible/master/intall.pkg.yml': line 9, column 5, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n # (Task-1) Checks if ansible_os_family == \"RHEL7\" and then Installs telnet on Remote Node\n - name: Install telnet on RHEL7 Server\n ^ here\n"}
Playbook
---
- hosts: all
gather_facts: true
become: yes
#become_user: ansible
become_method: sudo
tasks:
# (Task-1) Checks if ansible_os_family == "RHEL7" and then Installs telnet on Remote Node
- name: Install telnet on RHEL7 Server
yum: name=telnet state=present
when: ansible_os_family == "RedHat" and ansible_lsb.major_release|int == "7"
# (Task-2) Checks if ansible_os_family == "RHEL8" and then Installs telnet on Remote Node
- name: Install telnet on RHEL8 Server
package: name=telnet state=present
when: ansible_os_family == "RedHat" and ansible_lsb.major_release|int == "8"
How do I use my playbook to skip the RHEL7 and install the package on RHEL8 using dnf?
Thank you.
As already mentioned within the comments by #Zeitounator you may have a look into Conditionals based on ansible_facts since the fact respective variable is a string.
The following minimal example is working in an production environment.
- name: "Install telnet on RHEL-{{ ansible_distribution_major_version }} Remote Node"
yum:
name: telnet
state: present
when: ansible_distribution == 'RedHat' and ansible_distribution_major_version == '7'
- name: "Install telnet on RHEL-{{ ansible_distribution_major_version }} Remote Node"
yum:
name: telnet
state: present
when: ansible_distribution == 'RedHat' and ansible_distribution_major_version == '8'
This is possible because of Software management tools in RHEL 8
Although YUM v4 used in RHEL 8 is based on DNF, it is compatible with YUM v3 used in RHEL 7. For software installation, the yum command and most of its options work the same way in RHEL 8 as they did in RHEL 7.
This means that in your specific case by using the yum or package module
This modules manages packages on a target without specifying a package manager module (like ansible.builtin.yum, ansible.builtin.apt, …). It is convenient to use in an heterogeneous environment of machines without having to create a specific task for each package manager. package calls behind the module for the package manager used by the operating system discovered by the module ansible.builtin.setup.
there would be no need for a conditional check unless there would be differences in the availability of packages and or naming.
A single task like
- name: "Install telnet on RHEL-{{ ansible_distribution_major_version }} Remote Node"
package: # or even yum
name: telnet
state: present
should just work.
Depending on your infrastructure and to be prepared for future releases you could switch to the dnf module for RHEL-8 and RHEL-9 instances.
- name: "Install telnet on RHEL-{{ ansible_distribution_major_version }} Remote Node"
dnf:
name: telnet
state: present
when: ansible_distribution == 'RedHat' and ansible_distribution_major_version == '8'

Ansible: Failed to restart apache2.service: Connection timed out

I am using Ansible AWX to issue a restart command to restart an apache2 service on a host. The restart command is contained in a playbook.
---
- name: Manage Linux Services
hosts: all
tasks:
- name: Restart a linux service
command: systemctl restart '{{ service_name }}'
register: result
ignore_errors: yes
- name: Show result of task
debug:
var: result
OR
---
- name: Manage Linux Services
hosts: all
tasks:
- name: Restart a linux service
ansible.builtin.service:
name: '{{ service_name }}'
state: restarted
register: result
ignore_errors: yes
- name: Show result of task
debug:
var: result
However, when I run the command, I get the error below:
"Failed to restart apache2.service: Connection timed out",
"See system logs and 'systemctl status apache2.service' for details."
I have tried to figure out the issue, but no luck yet.
I later figured the cause of the issue.
Here's how I fixed it:
The restart command requires sudo access to run which was missing in my command.
All I have to do was to add the become: true command so that I can execute the command with root privileges.
So my playbook looked like this thereafter:
---
- name: Manage Linux Services
hosts: all
tasks:
- name: Restart a linux service
command: systemctl restart '{{ service_name }}'
become: true
register: result
ignore_errors: yes
- name: Show result of task
debug:
var: result
OR
---
- name: Manage Linux Services
hosts: all
tasks:
- name: Restart a linux service
ansible.builtin.service:
name: '{{ service_name }}'
state: restarted
become: true
register: result
ignore_errors: yes
- name: Show result of task
debug:
var: result
Another way if you want to achieve this on Ansible AWX is to tick the Privilege Escalation option in the job template.
If enabled, this runs the selected playbook in the job template as an administrator.
That's all.
I hope this helps
Restarting a service requires sudo privileges. Besides adding the 'become' directive, if you would like to prompt for the password, you can do so by passing the -K flag (note: uppercase K)
$ ansible-playbook myplay.yml -i hosts -u myname --ask-pass -K

package installation not considered in playbook

I got some trouble with automating an installation using ansible.
I use this role (https://github.com/elastic/ansible-elasticsearch) to install elasticsearch on my ubuntu 16.04 server.
The role depends on the package python-jmespath, as mentioned in the documentation.
The role DOES NOT install the package itsself, so i try to install it before role execution.
- hosts: elasticsearch_master_servers
become: yes
tasks:
- name: preinstall jmespath
command: "apt-get install python-jmespath"
- name: Run the equivalent of "apt-get update" as a separate step
apt:
update_cache: yes
- hosts: elasticsearch_master_servers
become: yes
roles:
- role: elastic.elasticsearch
vars:
...
When running the playbook i expect the python-jmespath package to be installed before execuction of role takes place, but role execution fails with
You need to install \"jmespath\" prior to running json_query filter"
When i check if the package is installed manually using dpkg -s python-jmespath i can see the package is installed correctly.
A second run of the playbook (with the package already installed) doesnt fail.
Do I miss an ansible configuration, that updates the list of installed packages during the playbook run ?
Am I doing something wrong in general ?
Thanks in advance
FWIW. It's possible to tag installation tasks and install the packages in the first step. For example
- name: install packages
package:
name: "{{ item.name }}"
state: "{{ item.state|default('present') }}"
state: present
loop: "{{ packages_needed_by_this_role }}"
tags: manage_packages
Install packages first
shell> ansible_playbook my-playbook.yml -t manage_packages
and then run the playbook
shell> ansible_playbook my-playbook.yml
Notes
This approach makes checking of the playbooks with "--check" much easier.
Checking idempotency is also easier.
With tags: [manage_packages, never] the package task will be skipped when not explicitly selected. This will speed up the playbook.

Ansible dnf module enable Fedora Copr repository

I want to enable a Fedora Copr repository with Ansible. More specifically I want to convert this command:
dnf copr enable ganto/lxd
Using an Ansible command module I overcome this problem but break the task's idempotence (if run again, the role should not make any changes) (changed_when: false is not an option).
- name: Enable Fedora Copr for LXD
command: "dnf copr enable -y ganto/lxd"
Also, I tried this:
- name: Install LXD
dnf:
name: "{{ item }}"
state: latest
enablerepo: "xxx"
with_items:
- lxd
- lxd-client
Where I test many variations for the option enablerepo without any success.
Is that possible using the dnf Ansible module (or something else)?
You can use creates to make your command idempotent; if the .repo file already exists then the task won't run:
- name: Enable Fedora Copr for LXD
command:
cmd: dnf copr enable -y ganto/lxd
creates: /etc/yum.repos.d/_copr_ganto-lxd.repo
(You'd have to check that enabled=1 manually)
$ cat /etc/yum.repos.d/_copr_ganto-lxd.repo
[ganto-lxd]
name=Copr repo for lxd owned by ganto
baseurl=https://copr-be.cloud.fedoraproject.org/results/ganto/lxd/fedora-$releasever-$basearch/
type=rpm-md
skip_if_unavailable=True
gpgcheck=1
gpgkey=https://copr-be.cloud.fedoraproject.org/results/ganto/lxd/pubkey.gpg
repo_gpgcheck=0
enabled=1
It is now possible thanks to https://docs.ansible.com/ansible/latest/collections/community/general/copr_module.html
The original snippet using command
- name: Enable Fedora Copr for LXD
command: "dnf copr enable -y ganto/lxd"
can be changed to
- name: Enable Fedora Copr for LXD
community.general.copr:
name: ganto/lxd
No, the Ansible dnf module doesn't support enabling Copr repositories.
You can add a task that tests if your Copr repository is already enabled that guards the Copr enable task.
Example:
shell:
cmd: |
dnf -C repolist enabled -v | grep '^Repo-id' | awk '$3 == "copr:copr.fedorainfracloud.org:ganto:lxd" {print "enabled"}'
warn: no
check_mode: no
changed_when: false
register: lxd_copr
- name: Enable Fedora Copr for LXD
command:
cmd: dnf -y copr enable ganto/lxd
warn: no
when: lxd_copr.stdout == ''
Notes:
double check the id of your copr repository as it's different from the short name you use to enable it
I set warn: no because ansible warns about all dnf commands (because it suggests to use the dnf module, if possible)
I set check_mode: no since it's safe to execute it even in --check mode
I set changed_when: false because the command doesn't change system state
Alternatively you can add and enable a Copr repository with the yum_repository Ansible module.
Example:
- name: enable copr
yum_repository:
name: "copr:copr.fedorainfracloud.org:{{ item[0] }}:{{ item[1] }}"
file: "_copr:copr.fedorainfracloud.org:{{ item[0] }}:{{ item[1] }}"
description: "{{ item[2] }}"
baseurl: "{{ copr_url }}/results/{{ item[0] }}/{{ item[1] }}/fedora-$releasever-$basearch/"
gpgkey: "{{ copr_url }}/results/{{ item[0] }}/{{ item[1] }}/pubkey.gpg"
gpgcheck: yes
enabled: yes
skip_if_unavailable: yes
vars:
#copr_url: https://copr-be.cloud.fedoraproject.org
copr_url: https://download.copr.fedorainfracloud.org
loop:
- [ganto, lxd, "Copr repo for LXD"]
This approximates the effect of the dnf copr enable ganto/lxd call. But there are some minor textual differences in the resulting .repo file (e.g. True vs. 1, keys with default values missing) that would lead to this task reporting changed if e.g. the repository was already enabled with dnf copr.
Also, this arguably has a higher maintenance overhead as one would have to track changes Copr introduces to its .repo files.

How to do best organization of install and delete in the same ansible playbook?

I try to figure how I must to construct an ansible playbooks where I can do some action and undo them (I can install or remove same packages; place file or remove this file).
If I create two ansible playbooks: delete.yml and install.yml. There are may be problem's like:
I added to installation someting, but don't change deletion
Example:
install.yml:
---
- name: Add x2go repository
apt_repository: repo='deb http://ppa.launchpad.net/x2go/stable/ubuntu precise main' state=present
apt_repository: repo='deb-src http://ppa.launchpad.net/x2go/stable/ubuntu precise main' state=present
when: ansible_os_family == "Debian"
tags:
- remote-access-x2go
- name: Install x2go application
apt: name=x2goserver update_cache=yes state=present
apt: name=x2goserver-xsession update_cache=no state=present
when: ansible_os_family == "Debian"
tags:
- remote-access-x2go
delete.yml:
---
- name: Add x2go repository
apt_repository: repo='deb http://ppa.launchpad.net/x2go/stable/ubuntu precise main' state=absent
apt_repository: repo='deb-src http://ppa.launchpad.net/x2go/stable/ubuntu precise main' state=present
when: ansible_os_family == "Debian"
tags:
- remote-access-x2go
- name: Install x2go application
apt: name=x2goserver update_cache=yes state=absent
apt: name=x2goserver-xsession update_cache=no state=absent
when: ansible_os_family == "Debian"
tags:
- remote-access-x2go
This is a very interesting idea. I have personally never tried the 'undoing' workflow, but I can see the nice things about this idea and would like to use it sometime. Here is what I would do.
In my ansible-role/defaults/main.yml I would define a variable flag
# defaults file for ansible-role
flag_undo: false
In my ansible-role/tasks/main.yml I would have
- name: task foo bar
command: falana dhimaka
- name: undoing task foo bar
command: undo falana dhimaka
when: flag_undo=true
So by default our flag is always false. So when installing things I would us the first command below to run my plays. And to uninstall I would use the second command.
ansible-playbook foo-play.yml
ansible-playbook foo-play.yml --extra-vars "flag_undo=true"
One approach that I use in some cases is to simply have lists of packages that you want installed and lists you want removed, then iterate over each list. I use this basic method not only for packages but other things as well, like users, groups, etc. For example, I have a "packages" role that has the following files in it:
vars/main.yml:
---
installed_system_packages:
- telnet
- screen
- postfix
latest_system_packages:
- glibc
removed_packages:
- sendmail
tasks/main.yml:
---
- name: Install system packages (latest)
yum: pkg={{ item }} state=latest
with_items: latest_system_packages
- name: Install system packages
action: yum pkg={{ item }} state=installed
with_items: installed_system_packages
- name: Remove unwanted packages
action: yum pkg={{ item }} state=removed
with_items: removed_packages
This way, if I decide that I no longer want a package like telnet installed I can just move it from installed_system_packages to removed_packages. Or if I want to ensure I'm running the latest version of screen I would simply move it to the latest_system_packages list. Then it's just a matter of re-running the role to have the changes applied.

Resources