Ansible apt-get install output - vagrant

I am using vagrant with an ansible playbook to automatically install a bunch of programs on an ubuntu image. One program is failing to install on the vagrant VM. In the Vagrant file I have
config.vm.provision :ansible do |ansible|
ansible.verbose = "vvv"
ansible.playbook = "provisioning/playbook.yml"
end
but the verbose output does not include the apt-get output. My playbook.yml looks like
---
- hosts: all
sudo: true
tasks:
- name: get vi
apt: state=latest name=vim
How can I see the console output of an individual (or all) apt-get install's on the VM since ansible instead outputs each install in the format
TASK: [Install vim] ***********************************************************
failed: [default] => {"failed": true}
...

Reproducing the stdout of apt
Here is how to reproduce the stdout of apt…
---
- name: 'apt: update & upgrade'
apt:
update_cache: yes
cache_valid_time: 3600
upgrade: safe
register: apt
- debug: msg={{ apt.stdout.split('\n')[:-1] }}
…with nice line breaks, thanks to .split('\n'), and omitting the last empty string with [:-1], all of which is Python string manipulation, of course.
"msg": [
"Reading package lists...",
"Building dependency tree...",
"Reading state information...",
"Reading extended state information...",
"Initializing package states...",
"Building tag database...",
"No packages will be installed, upgraded, or removed.",
"0 packages upgraded, 0 newly installed, 0 to remove and 0 not upgraded.",
"Need to get 0 B of archives. After unpacking 0 B will be used.",
"Reading package lists...",
"Building dependency tree...",
"Reading state information...",
"Reading extended state information...",
"Initializing package states...",
"Building tag database..."
]

You can register to a variable the output of the apt module execution and then print it.
- hosts: localhost
sudo: true
tasks:
- name: get vi
apt: state=latest name=vim
register: aptout
# show the content of aptout var
- debug: var=aptout

You can use directly stdout_lines, after registering the output as also shown above, but you must make sure that apt did output something, for example, it installed something, otherwise that element is not defined. Here's a possible example:
- name: Install Gnome Packages
become: yes
apt:
update_cache: yes
state: latest
pkg:
- gnome-tweaks
- dconf-editor
- guake
register: aptout
- debug: msg="{{ aptout.stdout_lines }}"
when: aptout.stdout_lines is defined

In the version of ansible I'm using at the moment, ansible-playbook -v seems sufficient to get apt output. Admittedly I haven't tested failures. The output is in the form of JSON, which makes it a bit hard to read (as the other answer works around).
The Ansible version I tested was 2.3.2.0.

Related

How to get latest package version with Ansible

I'm trying to replicate the command yum check-update package_name preferably with the Ansible yum module.
It provides an information to what version package would be updated after yum update execution (or ansible equivalent). For example:
root#host: $ yum check-update kernel
[...]
kernel.x86_64 3.10.0-1160.80.1.el7
[...]
root#host: $
I tried various combination of
- name: Xyz
ansible.builtin.yum:
list: updates
update_cache: true
But I can't limit it to a single package or pattern (like java*).
What I ended up with is ugly and slow (because of the download) workaround:
- name: Check latest available xyz version
yum:
name: xyz
state: latest
download_only: true
become: true
register: _result
- name: Register xyz version
set_fact:
latestXyz: "{{ _result.changes.updated[0][1] | regex_search('xyz-(.+).x86_64.*', '\\1') }}"
Is there any better way to achieve this?
One pretty easy way to achieve this is to run the yum module as you would to update the package, but enforce a dry run on it, with the check_mode option, and register the result of this task.
Inspecting the result, you'll realise that the module will list you the current version of the package and the version it would have been updated to, would it not have been run in dry run.
Given, for example
- yum:
name: expat
state: latest
check_mode: true
register: yum_latest_versions
Will populate the variable yum_latest_versions on a fedora:35 container with:
yum_latest_versions:
ansible_facts:
pkg_mgr: dnf
changed: true
failed: false
msg: 'Check mode: No changes made, but would have if not in check mode'
rc: 0
results:
- 'Installed: expat-2.5.0-1.fc35.x86_64'
- 'Removed: expat-2.4.9-1.fc35.x86_64'
Then to extract, you can indeed use a regex to search in the result:
- debug:
msg: >-
{{
(
yum_latest_versions.results
| map('regex_search', '^Installed: expat\-(.*)\.x86_64$', '\1')
).0.0
}}
Finally yields:
msg: 2.5.0-1.fc37

Install apache2 sqlite3 git - Ansible Loop

I have read all the similar questions here and yet I don't see a solution that fixed my issue.
This is my code:
---
- name: install apache2, sqlite3, git
tasks:
- name: Install list of packages
apt:
name: "{{ item }}"
state: installed
with_items:
- apache2
- sqlite3
- git
Here is the error:
ERROR! unexpected parameter type in action: <class 'ansible.parsing.yaml.objects.AnsibleSequence'>
The error appears to be in '/projects/challenge/fresco_loops/tasks/main.yml': line 2, column 3, but may
be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
---
- name: install apache2, sqlite3, git
^ here
I am not really an ansible expert and this is just one of the trainings we have to take. Thank you in advance.
firstly the best way to install multiple packages is shown below:
---
- name: install apache2, sqlite3, git
hosts: localhost # run locally
become: yes
tasks:
- name: Install list of packages
apt:
state: present
name:
- apache2
- sqlite3
- git
And a couple of final points:
"installed" is not a valid option for apt, try "present".
package installation requires sudo (become: yes).
To run the code above, which is locally on the node where the playbook sits, you need to use the command: ansible-playbook playbook.yml --ask-become-pass and enter the sudo password when prompted.
Secondly when I tried to run your code I got the following error
[DEPRECATION WARNING]: Invoking "apt" only once while using a loop via squash_actions is deprecated. Instead of using a loop to supply multiple items and specifying `name: "{{ item }}"`, please use `name: ['apache2', 'sqlite3',
'git']` and remove the loop. This feature will be removed in version 2.11. Deprecation warnings can be disabled by setting deprecation_warnings=False in ansible.cfg.
failed: [localhost] (item=['apache2', 'sqlite3', 'git']) => {"ansible_loop_var": "item", "changed": false, "item": ["apache2", "sqlite3", "git"], "msg": "value of state must be one of: absent, build-dep, fixed, latest, present, got: installed"}
When using Ansible 2.9.6, if you are using a different version that might explain it? Its always worth telling people what version you are using, in case the problem is version-specific.
Otherwise your code snippet is not representative of what is actually giving you the error.

Use ansible package module to work with apt and homebrew

I have two problems with creating playbook that works on linux and on macOS.
I have many steps like this one in my playbooks:
- name: install something
package:
name: [something_1, something_2, ...]
state: present
become: yes
It works nice for apt and yum, but when I tried to run this on macOS, homebrew complains:
Running Homebrew as root is extremely dangerous and no longer supported.
I couldn't find elegant way of fixing that in many places. Copy all tasks and use when clause seems overwhelming to me. Probably I could use become_user variable set to root/local_user dependent on distribution, but that's a lot changes too.
Second problem is with head-only formula (homebrew packages that can be installed only with --head flag). What if something_2 need to be installed with this flag? Again I could copy task and change package module to homebrew but that's a lot boilerplate.
Any help?
If you want a single set of tasks that flexible enough for for multiple Linux
package managers and macOS brew, the choice is either more logic or more
duplication.
These three patterns should help. They still have repetition and
boilerplate code, but that's the territory we're in with Ansible plays
for cross-platform.
Declare become: yes (root) globally for Linux only
Address packages that need platform-specific treatment as-needed with when
This might be --head for brew, or setting up a PPA for apt, etc
Map package name discrepancies with variables
For example: brew install ncurses, apt install libncurses5-dev, and dnf install ncurses-devel are all the same library.
1) Declare become: yes (root) globally for Linux only
For Linux hosts, switching to root for installation is the intended behavior.
For macOS a la Homebrew, installing as root is not good. So, we need become: no (false) when using brew, and become: yes (true) otherwise (for
Linux).
In your example the become directive is nested inside each task ("step"). To
prevent duplication, invoke become at a higher lexical scope, before the tasks
start. The subsequent tasks will then inherit the state of become, which is
set based on a conditional expression.
Unfortunately a variable for become at the root playbook scope will be
undefined and throw an error before the first task is run:
# playbook.yml
- name: Demo
hosts: localhost
connection: local
# This works
become: True
# This doesn't - the variable is undefined
become: "{{ False if ansible_pkg_mgr == 'brew' else True }}"
# Nor does this - also undefined
become: "{{ False if ansible_os_family == 'Darwin' else True }}"
tasks:
# ...
To fix this, we can store the tasks in another file and import them, or
wrap the tasks in a block. Either of these patterns will provide
a chance to declare become with our custom variable value in time for the
tasks to pick it up:
# playbook.yml
---
- name: Demo
hosts: localhost
connection: local
vars:
# This variable gives us a boolean for deciding whether or not to become
# root. It cascades down to any subsequent tasks unless overwritten.
should_be_root: "{{ true if ansible_pkg_mgr != 'brew' else false }}"
# It could also be based on the OS type, but since brew is the main cause
# it's probably better this way.
# should_be_root: "{{ False if ansible_os_family == 'Darwin' else True }}"
tasks:
# Import the tasks from another file, which gives us a chance to pass along
# a `become` context with our variable:
- import_tasks: test_tasks.yml
become: "{{ should_be_root }}"
# Wrapping the tasks in a block will also work:
- block:
- name: ncurses is present
package:
name: [libncurses5-dev, libncursesw5-dev]
state: present
- name: cmatrix is present
package:
name: cmatrix
state: present
become: "{{ should_be_root }}"
Now there is a single logic check for brew and a single before directive
(depending on which task pattern above is used). All tasks will be executed as
the root user, unless the package manager in use is brew.
2) Address packages that need platform-specific treatment as-needed with when
The Package Module is a great convenience but it's quite limited. By
itself it only works for ideal scenarios; meaning, a package that doesn't
require any special treatment or flags from the underlying package manager. All
it can do is pass the literal string of the package to install, the state, and
an optional parameter to force use of a specific package manager executable.
Here's an example that installs wget with a nice short task and only becomes
verbose to handle ffmpeg's special case when installed with brew:
# playbook.yml
# ...
tasks:
# wget is the same among package managers, nothing to see here
- name: wget is present
when: ansible_pkg_mgr != 'brew'
package:
name: wget
state: present
# This will only run on hosts that do not use `brew`, like linux
- name: ffmpeg is present
when: ansible_pkg_mgr != 'brew'
package:
name: ffmpeg
state: present
# This will only run on hosts that use `brew`, i.e. macOS
- name: ffmpeg is present (brew)
when: ansible_pkg_mgr == 'brew'
homebrew:
name: ffmpeg
# head flag
state: head
# --with-chromaprint --with-fdk-aac --with-etc-etc
install_options: with-chromaprint, with-fdk-aac, with-etc-etc
The play above would produce this output for ffmpeg against a Linux box:
TASK [youtube-dl : ffmpeg is present] ******************************************
ok: [localhost]
TASK [youtube-dl : ffmpeg is present (brew)] ***********************************
skipping: [localhost]
3) Map package name discrepancies with variables
This isn't specifically part of your question but it's likely to come up next.
The Package Module docs also mention:
Package names also vary with package manager; this module will not "translate"
them per distro. For example libyaml-dev, libyaml-devel.
So, we're on our own to handle cases where the same software uses different
names between package manager platforms. This is quite common.
There are multiple patterns for this, such as:
Use separate variable files for each OS/distro and import them
conditionally
Use a role with its own variables
Use the same package manager across platforms, such as Homebrew or
Conda
Compile everything from source via git
None of them are very pleasant. Here is an approach using a role. Roles do
involve more boilerplate and directory juggling, but in exchange they provide
modularity and a local variable environment. When a set of tasks in a role
requires more finagling to get right, it doesn't end up polluting other task
sets.
# playbook.yml
---
- name: Demo
hosts: localhost
connection: local
roles:
- cmatrix
# roles/cmatrix/defaults/main.yml
---
ncurses:
default:
- ncurses
# Important: these keys need to exactly match the name of package managers for
# our logic to hold up
apt:
- libncurses5-dev
- libncursesw5-dev
brew:
- pkg-config
- ncurses
# roles/cmatrix/tasks/main.yml
---
- name: cmatix and its dependencies are present
become: "{{ should_be_root }}"
block:
- name: ncurses is present
package:
name: '{{ item }}'
state: latest
loop: "{{ ncurses[ansible_pkg_mgr] | default(ncurses['default']) }}"
- name: cmatrix is present
when: ansible_pkg_mgr != 'brew'
package:
name: cmatrix
state: present
The task for ncurses looks for an array of items to loop through keyed by the
corresponding package manager. If the package manager being used is not defined
in the variable object, a Jinja default filter is employed to reference the
default value we set.
With this pattern, adding support for another package manager or additional
dependencies simply involves updating the variable object:
# roles/cmatrix/defaults/main.yml
---
ncurses:
default:
- ncurses
apt:
- libncurses5-dev
- libncursesw5-dev
# add a new dependency for Debian
- imaginarycurses-dep
brew:
- pkg-config
- ncurses
# add support for Fedora
dnf:
- ncurses-devel
Combining everything into a real play
Here's a full example covering all three aspects. The playbook has two roles
that each use the correct become value based on a single variable. It also
incorporates an special cases for cmatrix and ffmpeg when installed with
brew, and handles alternate names for ncurses between package managers.
# playbook.yml
---
- name: Demo
hosts: localhost
connection: local
vars:
should_be_root: "{{ true if ansible_pkg_mgr != 'brew' else false }}"
roles:
- cmatrix
- youtube-dl
# roles/cmatrix/defaults/main.yml
ncurses:
default:
- ncurses
apt:
- libncurses5-dev
- libncursesw5-dev
brew:
- pkg-config
- ncurses
dnf:
- ncurses-devel
# roles/cmatrix/tasks/main.yml
---
- name: cmatrix and dependencies are present
# A var from above, in the playbook
become: "{{ should_be_root }}"
block:
- name: ncurses is present
package:
name: '{{ item }}'
state: latest
# Get an array of the correct package names to install from the map in our
# default variables file
loop: "{{ ncurses[ansible_pkg_mgr] | default(ncurses['default']) }}"
# Install as usual if this is not a brew system
- name: cmatrix is present
when: ansible_pkg_mgr != 'brew'
package:
name: cmatrix
state: present
# If it is a brew system, use this instead
- name: cmatrix is present (brew)
when: ansible_pkg_mgr == 'brew'
homebrew:
name: cmatrix
state: head
install_options: with-some-option
# roles/youtube-dl/tasks/main.yml
---
- name: youtube-dl and dependencies are present
become: "{{ should_be_root }}"
block:
- name: ffmpeg is present
when: ansible_pkg_mgr != 'brew'
package:
name: ffmpeg
state: latest
- name: ffmpeg is present (brew)
when: ansible_pkg_mgr == 'brew'
homebrew:
name: ffmpeg
state: head
install_options: with-chromaprint, with-fdk-aac, with-etc-etc
- name: atomicparsley is present
package:
name: atomicparsley
state: latest
- name: youtube-dl is present
package:
name: youtube-dl
state: latest
The result for Ubuntu:
$ ansible-playbook demo.yml
[WARNING]: provided hosts list is empty, only localhost is available. Note that
the implicit localhost does not match 'all'
PLAY [Demo] ********************************************************************
TASK [Gathering Facts] *********************************************************
ok: [localhost]
TASK [cmatrix : ncurses is present] ********************************************
ok: [localhost] => (item=libncurses5-dev)
ok: [localhost] => (item=libncursesw5-dev)
TASK [cmatrix : cmatrix is present] ********************************************
ok: [localhost]
TASK [cmatrix : cmatrix is present (brew)] *************************************
skipping: [localhost]
TASK [youtube-dl : ffmpeg is present] ******************************************
ok: [localhost]
TASK [youtube-dl : ffmpeg is present (brew)] ***********************************
skipping: [localhost]
TASK [youtube-dl : atomicparsley is present] ***********************************
ok: [localhost]
TASK [youtube-dl : youtube-dl is present] **************************************
ok: [localhost]
PLAY RECAP *********************************************************************
localhost : ok=6 changed=0 unreachable=0 failed=0 skipped=2 rescued=0 ignored=0

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.

How can I get the installed YUM packages with Ansible?

I am trying to get all the installed YUM packages on an RHEL machine. I can easily get it through using shell commands which is not idempotent and would like to use the YUM command instead.
The shell command works fine:
- name: yum list packages
shell: yum list installed > build_server_info.config
But when I try to use the YUM command, it just executes, but it does not give any results:
- name: yum_command
action: yum list=${pkg} list=available
I can easily get it through using shell commands which is not idempotent
You can't really talk about idempotence, when you are querying the current state of a machine.
"Idempontent" means that the task will ensure the machine is in the desired state no matter how many times you run a certain task.
When you query current state, you don't describe the desired state. No matter what you do, what method you use, the term "idempotent" is just not applicable.
Regarding your example, which does not give you results - you have repeated twice the same argument list and the task should fail (it doesn't, which looks like an Ansible quirk).
To get a list of installed packages, you should use:
- name: yum_command
yum:
list=installed
register: yum_packages
- debug:
var: yum_packages
It saves a list of dictionaries describing each package to a variable yum_packages.
You can then use a JSON Query Filter to get a single package (tar):
- debug: var=item
with_items: "{{yum_packages|json_query(jsonquery)}}"
vars:
jsonquery: "results[?name=='tar']"
to get a result like this:
"item": {
"arch": "x86_64",
"epoch": "2",
"name": "tar",
"nevra": "2:tar-1.26-31.el7.x86_64",
"release": "31.el7",
"repo": "installed",
"version": "1.26",
"yumstate": "installed"
}
Or only its version:
- debug: var=item
with_items: "{{yum_packages|json_query(jsonquery)}}"
vars:
jsonquery: "results[?name=='tar'].version"
"item": "1.26"
Since Ansible 2.5, you can also use the package_facts module: it will gather the list of installed packages as Ansible facts.
Example from the CPU:
- name: get the rpm package facts
package_facts:
manager: rpm
- name: show them
debug: var=ansible_facts.packages
Well, the official Ansible documentation for the yum module describes list as:
"Various (non-idempotent) commands for usage with /usr/bin/ansible and not playbooks."
so you're going to be out of luck with finding an idempotent list invocation.
If you just want to suppress the changed output, set the changed_when parameter to False.
(Also, having the duplicate list parameter is suspicious.)
If your requirement is to check for only one package, and based on that you want to execute another task, you can use package_facts module as
- name: install docker community edition in rhel8
hosts: localhost
connection: local
tasks:
- name: Gather the rpm package facts
package_facts:
manager: auto
- name: check if docker-ce is already installed
debug:
var: ansible_facts.packages['docker-ce']
- name: using command module to install docker-ce
command: yum install docker-ce --nobest -y
when: "'docker-ce' not in ansible_facts.packages"
register: install_res
- name: whether docker is installed now
debug:
var: install_res

Resources