Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I ensure that an Ansible shell command executes only once per host?

Tags:

shell

ansible

I want to be able to guarantee that after a long running shell command is executed successfully on a given host, it is not executed in subsequent playbook runs on that host.

I was happy to learn of the creates option to the Ansible shell task (see http://docs.ansible.com/ansible/shell_module.html); using this I can create a file after a shell command succeeds and not have that command execute in future runs:

- name: Install Jupiter theme
  shell: wp theme install ~/downloads/jupiter-theme.zip --activate
         && touch ~/ansible-flags/install-jupiter-theme
  args:
    creates: ~/ansible-flags/install-jupiter-theme

As you can see, I have a directory ansible-flags for these control files in the home directory of my active user (deploy).

However, this is quite a kludge. Is there a better way to do this?

Also, the situation gets a lot more complicated when I need to run a step as a different user; in that case, I don't have access to the deploy home directory and its subdirectories. I could grant access, of course, but that's adding even more complexity. How would you recommend I deal with this?

like image 999
Keith Bennett Avatar asked Jul 07 '16 12:07

Keith Bennett


People also ask

How do I make an Ansible run only once?

The easiest way to run only one task in Ansible Playbook is using the tags statement parameter of the “ansible-playbook” command. The default behavior is to execute all the tags in your Playbook with --tags all .

How do I control Ansible playbook only on specific hosts?

Using the --limit parameter of the ansible-playbook command is the easiest option to limit the execution of the code to only one host. The advantage is that you don't need to edit the Ansible Playbook code before executing to only one host.


2 Answers

Your playbook should be idempotent!
So either task should be safe to be executed multiple times (e.g. echo ok) or your should check whether you should execute it at all.
Your task may look like this:

- name: Install Jupiter theme
  shell: wp theme is-installed jupiter || wp theme install ~/downloads/jupiter-theme.zip --activate

It will check if the jupiter theme is installed and will run wp theme install only if theme is not installed.
But this will mark the result of task as changed in any case.
To make it nice, you can do this:

- name: Install Jupiter theme
  shell: wp theme is-installed jupiter && echo ThemeAlreadyInstalled || wp theme install ~/downloads/jupiter-theme.zip --activate
  register: cmd_result
  changed_when: cmd_result.stdout != "ThemeAlreadyInstalled"

First, it will check if the jupiter theme is already installed.
If this is the case, it will output ThemeAlreadyInstalled and exit.
Otherwise it will call wp theme install.
The task's result is registered into cmd_result.
We user changed_when parameter to state the fact that if ThemeAlreadyInstalled ansible shouldn't consider the task as changed, so it remains green in your playbook output.

If you do some preparation tasks before wp theme install (e.g. download theme), you may want to run the test as separate task to register the result and use when clauses in subsequent tasks:

- name: Check Jupiter theme is installed
  shell: wp theme is-installed jupiter && echo Present || echo Absent
  register: wp_theme
  changed_when: false

- name: Download theme
  get_url: url=...
  when: wp_theme.stdout == "Absent"

- name: Install Jupiter theme
  shell: wp theme install ~/downloads/jupiter-theme.zip --activate
  when: wp_theme.stdout == "Absent"
like image 130
Konstantin Suvorov Avatar answered Oct 14 '22 00:10

Konstantin Suvorov


Ansible itself is stateless, so whatever technique you use needs to be implement by yourself. There is no easier way like telling Ansible to only ever run a task once.

I think what you have already is pretty straight forward. Maybe use another path to check. I'm pretty sure the wp theme install will actually extract that zip to some location, which you then can use together with the creates option.

There are alternatives but nothing that makes it easier:

  • Create a script in any language you like, that checks for the theme and if it is not installed, install it. Instead of the shell task then you would execute the script with the script module.

  • Install a local fact in /etc/ansible/facts.d which checks if the theme in already installed. Then you could simply apply a condition based on that fact to the shell task.

  • Set up fact caching, then register the result of your shell task and store it as a fact. With fact caching enabled you can access the stored result on later playbook runs.

It only gets more complicated. The creates option is perfect for this scenario and if you use the location where the wp command extracted the zip to, it also is pretty clean.

Of course you need to grant access to that file, if you have a need to access it from another user account. Storing stuff in a users home directory with the permissions set to only be readable by the owner indeed is no ideal solution.

like image 35
udondan Avatar answered Oct 13 '22 23:10

udondan