Ansible "template error" using variable in formatted string

Viewed 103

This ansible task is supposed to check whether an specific version of an npm package is installed and, if not, install it. The when condition I am using involves a formatted string with a defined variable, pm2_version, yet for some reason my attempt to use that variable in the test is causing an error.

I expect that the expression f'pm2@{pm2_version}' should create a formatted string, but clearly that expectation is incorrect. What is the correct way to use a variable here?

Here are the relevant tasks:

- name: List installed packages
  shell: 'npm list -g --depth=0'
  args:
    executable: /bin/bash
  environment:
    PATH: "{{ ansible_env.HOME }}/.nvm/versions/node/{{node_version}}/bin:{{ ansible_env.PATH }}"
  register: npm_package_list

- name: Install pm2
  shell: 'npm i -g pm2@{{pm2_version}}'
  args:
    executable: /bin/bash
  environment:
    PATH: '{{ ansible_env.HOME }}/.nvm/versions/node/{{node_version}}/bin:{{ ansible_env.PATH }}'
    when: npm_package_list.stdout.find(f'pm2@{pm2_version}') == -1

But running the playbook, I get the following error:

FAILED! => {"msg": "The conditional check 'npm_package_list.stdout.find(f'pm2@{pm2_version}') == -1' failed. The error was: template error while templating string: expected token ',', got 'string'. 
1 Answers

What you are looking to do is an anti-pattern in Ansible.
Ansible is based on idempotency, that is to say, in your case, if the npm package is already installed at the version requested version, Ansible will do nothing, and return you an ok status while it will return you a changed status in case it had to install it.

But for this, you have to use the dedicated npm module:

- npm:
    name: "{{ 'pm2@%s' | format(pm2_version) }}"
    global: yes
    executable: >- 
      {{ ansible_env.HOME }}/.nvm/versions/node/{{ node_version }}/bin/npm

Still, your two (maybe three) issues, here, are:

  • Jinja is not implementing all the functionalities of Python, so there are features that will not work, like find on a string
  • For the same reason, f-strings are not supported
  • (Possibly a typo in the question) Your when close is wrongly indented.

In order to find a substring in a string, you can use the in, or in your case not in test.
In order to have a f-string formatting, you can use the format filter, or just use a string concatenation ~.

So, your condition should look like:

when: "'pm2@%s' | format(pm2_version) not in npm_package_list.stdout"

or

when: "'pm2@' ~ pm2_version not in npm_package_list.stdout"

Given the task:

- debug:
    var: "'pm2@%s' | format(pm2_version) not in npm_package_list.stdout"
  vars:
    pm2_version: 1.2.4
    npm_package_list:
      stdout: pm2@1.2.3

Which yields:

ok: [localhost] => 
  '''pm2@%s'' | format(pm2_version) not in npm_package_list.stdout': true
Related