Home > Software design >  How to use 'shell' output stored in a variable in further task in Ansible correctly?
How to use 'shell' output stored in a variable in further task in Ansible correctly?

Time:09-14

Lately I've been tinkering with Ansible and want to achieve following on my test node using Ansible:

  • Clone a repo to the Remote Node
  • grep a packagelist from a textfile within the git repo
  • Load the tools from the packagelist into an Ansible variable
  • Install these tools via the Ansible package module or apt module.

So far I've got this:

---
- hosts: all
  become: true
  vars:
    username: foobar

  tasks:
    - name: Install git package
      package:
        name: git
        state: present

    - name: Clone GIT Repo
      git:
        repo: https://github.com/somerepo/.dotfiles.git
        dest: /home/{{ username }}/.dotfiles
        clone: yes
        version: master
        update: yes
        force: yes

    - name: Set permission on folder ~/.dotfiles
      file:
        dest: /home/{{ username }}/.dotfiles
        recurse: yes
        owner: "{{ username }}"
        group: "{{ username }}"
        mode: "0775"

    - name: Extract list of needed tools from install.sh
      shell:
        cmd: grep "^tools=" /home/{{ username }}/.dotfiles/install.sh | tr '"' " " | cut -c7-
      register: grep_output

    - name: Install following packages "{{ grep_output }}"
      apt:
        name: "{{ grep_output }}"
        state: present
        update_cache: yes

The shell command gathers a list with tools:

grep "^tools=" install.sh |tr '"' " " |  cut -c7-

with the STDOUT output

zsh neovim vim tmux ranger stow wget curl git fzf

Those values I want to store in an Ansible variable and install it via package plugin.

When I run the playbook on my test VM

PLAY [all] *******************************************************************************************************************

TASK [Gathering Facts] *******************************************************************************************************
[WARNING]: Platform linux on host vm-mint21 is using the discovered Python interpreter at /usr/bin/python3.10, but future
installation of another Python interpreter could change the meaning of that path. See https://docs.ansible.com/ansible-
core/2.12/reference_appendices/interpreter_discovery.html for more information.
ok: [vm-mint21]

TASK [Install git package] ***************************************************************************************************
ok: [vm-mint21]

TASK [Clone GIT Repo] ********************************************************************************************************
changed: [vm-mint21]

TASK [Set permission on folder ~/.dotfiles] **********************************************************************************
changed: [vm-mint21]

TASK [Extract list of needed tools from install.sh] **************************************************************************
changed: [vm-mint21]

TASK [Install following packages "{'changed': True, 'stdout': ' zsh neovim vim tmux ranger stow wget curl git fzf ', 'stderr': '', 'rc': 0, 'cmd': 'grep "^tools=" /home/soeren/.dotfiles/install.sh | tr \'"\' " " | cut -c7-', 'start': '2022-09-12 22:33:37.056914', 'end': '2022-09-12 22:33:37.061264', 'delta': '0:00:00.004350', 'msg': '', 'stdout_lines': [' zsh neovim vim tmux ranger stow wget curl git fzf '], 'stderr_lines': [], 'failed': False}"] ***
fatal: [vm-mint21]: FAILED! => {"changed": false, "msg": "argument 'package' is of type <class 'dict'> and we were unable to convert to list: <class 'dict'> cannot be converted to a list"}

PLAY RECAP *******************************************************************************************************************
vm-mint21                  : ok=5    changed=3    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0

I get following error:

argument 'package' is of type <class 'dict'> and we were unable to convert to list: <class 'dict'> cannot be converted to a list"

CodePudding user response:

To narrow down such issue one may have a look into the following example

---
- hosts: localhost
  become: false
  gather_facts: false

  tasks:

  - name: Create STDOUT output
    command: 'echo "1 2 3 4 5 6"'
    register: result

  - name: Show full result
    debug:
      var: result

  - name: Show '.stdout'
    debug:
      msg: "The result in '.stdout': {{ result.stdout }} is of type {{ result.stdout | type_debug }}"

resulting into an output of

TASK [Show full result] ****************************************************************************
ok: [localhost] =>
  result:
    changed: true
    cmd:
    - echo
    - 1 2 3 4 5 6
    delta: '0:00:00.009634'
    end: '2022-09-12 23:00:00.788265'
    failed: false
    rc: 0
    start: '2022-09-12 23:00:00.778631'
    stderr: ''
    stderr_lines: []
    stdout: 1 2 3 4 5 6
    stdout_lines:
    - 1 2 3 4 5 6

TASK [Show '.stdout'] ****************************************************************************
ok: [localhost] =>
  msg: 'The result in ''.stdout'': 1 2 3 4 5 6 is of type AnsibleUnsafeText'

In this way one can debug the Return Values of an Ansible module

Ansible modules normally return a data structure that can be registered into a variable, or seen directly when output by the ansible program. Each module can optionally document its own unique return values (visible through ansible-doc and on the main docsite).

in a variable which became registered.

Registered variables may be simple variables, list variables, dictionary variables, or complex nested data structures. The documentation for each module includes a RETURN section describing the return values for that module.


For your specific case you can see that the shell module has a <yourVarName>.stderr and <yourVarName>.stdout, whereby you are interested in .stdout only. The type of the registered output is text marked as unsafe so it will not become templated.

To feed the registered output to the next module package or apt, which will require a list, you can just use the from Zeittounator within the comments mentioned name: "{{ grep_output.stdout.split(' ') }}" line. It will split the string into a list based on a delimiter.

CodePudding user response:

You can modify your last task as below. Using "split" function you can convert string to a list.

- name: Install following packages "{{ grep_output }}"
  apt:
    name: "{{ item }}"
    state: present
    update_cache: yes
  loop: "{{ grep_output.stdout.split() }}"
  • Related