[SQUASH] backport tripleo-container-manage to stable/train

This is a squash of 32 commits to facilitate the backport of
tripleo-container-manage and its dependencies.

Introduce tripleo-container-manage role

This is a first ieration of the role, but there is still a long TODO,
that will come later in separated patches:
- Add molecule testing
- In podman.yaml, add cpuset_cpus with parity of what is in paunch
- Remove containers that are:
  - managed by tripleo-ansible (using the container_label flag)
  - not in the container-startup-config
- Print stdout when containers start as it was done with paunch

Story: 2006732
Task: 37165

Co-Authored-By: Kevin Carter <kecarter@redhat.com>
Co-Authored-By: Alex Schultz <aschultz@redhat.com>

Depends-On: https://review.opendev.org/#/c/702144/
Change-Id: I2f88caa8e1c230dfe846a8a0dd9f939b98992cd5
(cherry picked from commit a191a2d600)

tripleo-container-manage: set some defaults

Set defaults that are needed to use the role outside of THT more easily.

Change-Id: Id67cf06c85a2a6b50e6494b1a66f534ccb06c4a7
(cherry picked from commit 609d7895a1)

Move the filters plugin to the core plugins location

This change is a workaround for a zuul issue which moves the
nested ansible role plugin to the core plugins directory so
that it is not creating a gate conflict.

Change-Id: I9f959803381063502b4d15980b14c3416ffa153f
Signed-off-by: Kevin Carter <kecarter@redhat.com>
(cherry picked from commit e2719131db)

Revert "Workaround for ansible-lint installation failure"

Backport note: this is a second backport of the same patch
since now it includes the change in tripleo-container-manage
role that is being backported to stable/train.

This reverts the disabling of the ansible-lint test from
commit cffd4fc9d4 and updates
ansible-lint to the fixed version.

Included are fixes for ansible-lint test failures which
got merged as part of I2f88caa8e1c230dfe846a8a0dd9f939b98992cd5
while the lint test was disabled.

Change-Id: I37100f5e1764a5cd2cb8df82ae963e673ca0a8da
(cherry picked from commit 28e105c056)

tripleo-container-manage: few improvements

- Add and use variables to make the role more flexible:
  tripleo_container_manage_config,
  tripleo_container_manage_config_id
  tripleo_container_manage_debug
  tripleo_container_manage_config_pattern (and rename hashed_files var)

  With these vars, the role can pretty much be used outside of TripleO.

- Show logs of config data generation if debug is enabled

- Do not run the "podman exec" tasks in check mode

- Remove the dependency on the "step" variable

Change-Id: I28ee31b723f27c392f880676aaae9368906cf45f
(cherry picked from commit 9c69840640)

Adds new molecule testing for tripleo-container-manage

This test ensure the "create" part is working fine with an easy and
simple container. The container-create and default scenario have been
consoldated so its running one complete test for now.

Change-Id: I9139c7b63c15739a1a95d913acb1128af299ce97
Co-Authored-By: Emilien Macchi <emilien@redhat.com>
Signed-off-by: Kevin Carter <kecarter@redhat.com>
(cherry picked from commit 414f47cc32)

tripleo-container-manage: add check tasks to the molecule playbook

Verify that the "fedora" container exists and has the right infos.

Change-Id: If78a254564cff502b49769b0b401d2efdac8cb23
(cherry picked from commit c20ab42015)

Molecule job for testing plugins and modules

and remove ansible_facts from podman_container_info module
Change-Id: I0c768bc6168363fa3758562f9f053aa9ab85236b

(cherry picked from commit 2d42082737)

tripleo-container-manage - first support for idempotency

- Implements a new helper to figure out if the existing containers on
  the hosts need to be removed (and re-created later).
  The helper will remove the container if:
    - the container is managed by tripleo_ansible (+ other conditions later)
    - a running container isn't in the config
    - the container has no config_data Label
    - the config_data changed for the container

- Restart the systemd service for the container if the podman_container
  module reported as changed (note the podman_container isn't yet
  idempotent but we're working on it in a separated patch)

- Fix the healthcheck & timer systemd files to be attached to the right
  service (with tripleo_ prefix)

Story: #2006732
Task: #37163
Change-Id: I5081c918b47dcb9f3629a3649fdf33d17668c1ff
(cherry picked from commit 7e41e0642d)

tripleo-container-manage: include_tasks to speed things up

Change-Id: I79d48fef7552f72c8fc0ebbad35d98cfc618b114
(cherry picked from commit 57411934b7)

tripleo-container-manage: introduce concurrency

Co-Authored-By: Alex Schultz <aschultz@redhat.com>
Change-Id: I1e5e941558c492b050e4db542703e322707dbbbd
(cherry picked from commit 2f8f0fc027)

tripleo-container-manage: fix log_opt

Put the right variables to have proper logging.

Change-Id: I60a14721ed978dddbebfd28f830fb2c50f326ce0
(cherry picked from commit e7f71c352e)

tripleo-container-manage: some nits fixed

Change-Id: I4006db3f5e3092aefeb8a0e50819dda11931630b
(cherry picked from commit 73e49888f7)

tripleo-container-manage: fix exec check

Fix the task which check if the container where the exec happens is
actually running.

Also podman_containers needs to be refreshed at every start_order to get
new container informations status.
So moving podman_containers from main, and putting it where we need it:
- before processing the list of containers to delete
- when start_order playbook starts

Co-Authored-By: Sagi Shnaidman <sshnaidm@redhat.com>
Change-Id: I2afb71288208c8b97763caa832c94e06e1b9457c
(cherry picked from commit e68d4b42a2)

tripleo-container-manage: port paunch-services

paunch-services used to be useful for container start/stop ordering,
when on the host some containers are managed by Pacemaker and some
others by Paunch.

We need to keep that feature so we are now porting these scripts and
services into tripleo-ansible.

These files where managed by Paunch:
%{_libexecdir}/paunch-container-shutdown
%{_libexecdir}/paunch-start-podman-container
%{_unitdir}/paunch-container-shutdown.service
%{_presetdir}/91-paunch-container-shutdown.preset
%{_unitdir}/netns-placeholder.service
%{_presetdir}/91-netns-placeholder.preset

Now we handle them via Ansible now, and cleanup the Paunch version.
It creates the exact same files from:
https://github.com/rdo-packages/paunch-distgit

This feature is disabled by default and will be explicitely enabled by
THT later. Molecule tests enable it though for testing coverage.

Story: 2006732
Task: 37382
Change-Id: I4f79429baab50bc0199fb65fe84641908d83935d
(cherry picked from commit 6e76f444df)

tripleo-container-manage: use async

- Add a new filter 'haskey' which returns container data with a specific
  config key. The filter takes a list of dicts and returns the dicts
  which have a certain key given in parameter with 'attribute'.
  If 'reverse' is set to True, the returned list won't contains dicts
  which have the 'attribute' value.
  If 'any' is set to True, the returned list will match any value in the
  list of 'value' parameter which has to be a list.

- Make the exec and create playbook using ansible "async".
  (WIP is to re-add the podman_container.changed in systemd playbook).

Note: we don't manage Systemd resources with async, since it exposes
race conditions at the systemd level.

Change-Id: Ice92cd5f90039e685c171e9035f929349a67ff2c
(cherry picked from commit e26f817597)

tripleo-container-manage: fix duplicated loop vars

So we don't have the Ansible warning saying the loop var is already used
for another loop.

Change-Id: Ie28857cb712d2133aad4a67ae6942fcbbb1a6aee
(cherry picked from commit 80e0476f78)

tripleo-container-manage: skip some tasks in check mode

Change-Id: I5e68fdb2d872c741f00a1a24bac33112ba630f69
(cherry picked from commit f506dd6994)

tripleo-container-manage: add no_log to podman create

So we don't leak confidential informations like passwords and others in
the logs.
Note that if debug is enabled, the infos are displayed in the ansible
logs.

Change-Id: I8473c9118dbce2b04eeb2c01bcc1e55232325a67
(cherry picked from commit ac8cff4ebb)

Improve molecule tests for tripleo-container-manage (systemd)

Change-Id: Id14ad4876bdf7dcc979d5ecf4fe2b4751c635b88
(cherry picked from commit 484faa7e80)

fix typo

Change-Id: I0a970ae73f01c6ff181a67367cf668091748fc9c
Signed-off-by: Kevin Carter <kecarter@redhat.com>
(cherry picked from commit 3720b41a7f)

tripleo-container-manage: restart systemd service if container changed

If a podman_container resource changed, it'll be added to a fact, that
later will be used when it comes to figure our if a systemd service
needs to be restarted or not.

It introduces a new filter: list_of_keys.
This filter takes in input a list of dictionaries and for each of them
it will add the key to list_of_keys and returns it.

Change-Id: I0285b006c015f6cd223615ebdad52286f7683a87
(cherry picked from commit 0a037c65f7)

tripleo-container-manage: some improvements

- Improve logging in some tasks, to display what container is managed
- Use no_log for the tasks which leak all infos about containers
  (config_data). If debug is enabled, the logs will show all the
  config_data.
- Move the /etc/sysconfig/podman_drop_in tasks into shutdown.yaml so it
  runs once and it's staying close to the other tasks related to why we
  have this file (manage shutdown/start order when reboot).

Change-Id: I56896f92d58db900fd2ea06281d89f75e8d53a17
(cherry picked from commit 6743f75863)

tripleo-container-manage: create ansible-managed dropin file

If a deployment has containers managed by tripleo-ansible and not
paunch, and an operator would try to run paunch on the host, we want to
send a warning because paunch will remove all containers managed by
tripleo-ansible and then redeploy the containers with managed_by=paunch.

We could change the default of manage_by in Paunch (currently
managed_by=paunch) to something else e.g. tripleo, which would also be
the managed_by of tripleo-ansible; so both paunch and tripleo-ansible
could be used on the same host. However, we decided that when
tripleo-ansible is used to deploy the containers, paunch could not be
used anymore on that host.

Therefore, we create a file that will be checked by paunch and if
present, paunch CLI will show a warning.

Change-Id: I722cb8faa3b7eee81b418da83451bf802351dd79
(cherry picked from commit db1b13c962)

tripleo-container-manage: remove no_log for create/exec tasks

1. Remove no_log for the create and exec tasks. It's always useful to
   see what is being run.

2. Enable podman_container debug, so we see what commands are being run.

Change-Id: If728788293dd64622cf95da840b60e271197e9a0
(cherry picked from commit 89e4adf2ae)

Add missing ExecReload in container service unit file

It may happen that we want to just reload the container. Before this
patch, it was a "stop and start", while podman has the "podman kill"
available, accepting the HUP signal.

Doing so allows other automated tools to actually just "reload" the
container as we would do for a standard service.

Change-Id: I35eff80f7637b013d3a1a831289ec9b1e0f81431
(cherry picked from commit 2a2bed6f5c)

Introduce unit tests structure for tripleo-ansible filters

Change-Id: Ie2fea14d2cbfb2c0b78cdc3064df0a558fa28a4c
(cherry picked from commit f90a6d42b3)

Fix case in filters when Labels is None

When Labels is None it's not managed by tripleo container,
then skip it.
Change-Id: Ib82c4d28c462abb3f1a5ccb7d5137ec6059b2665

(cherry picked from commit 1acc95211d)

tripleo-container-manage: include more arguments for container exec

Container execs can be more complex than just a user and a command.
It can also use --env and --privileged.

- container_exec_cmd is a new filter that will help to build the
  container exec command from the container data.
- list_or_dict_arg is an utility taken from openstack/paunch which
  allows to build a command and its arguments with list or dict data.

This patch will allow the container exec to work fine when they have
environment variables, like it's the case for Keystone bootstrap.

Change-Id: I15e0b518936b37e26799dbda9677a248cf17ff3c
(cherry picked from commit 55d5363b4a)

Add SyslogIdenfier to healthcheck systemd unit

Adding this new field will allow to filter all healthcheck logs using
the Idenfier value.

For instance, using journalctl, you would be able to run this:
`journalctl -t healthcheck_collectd'

It will also allow to get a dedicated file out of (r)syslog if needed.

Change-Id: Icdc5caf4cedc46291a807c39c0a31c74955a4a74
Closes-Bug: #1856573
(cherry picked from commit 49858c5265)

Allow to run tripleo-container-manage in check mode

Change-Id: I3350a43805b4a148f64de393716c26b0158fcff4
(cherry picked from commit 6513a4bed8)

tripleo-container-manage: fix config_data in Config/Labels

config_data should not contain the container name, just the actual
config data which is the value of the container_data dict.

It removes the add of start_order into the compare, this isn't necessary
and breaks idempotency for containers which don't have start_order in
their config_data.
Also adds more unit tests to cover all situations handled by the filter.

Change-Id: I0b64bf34c8f7498128b3b1fceb7c727f8544cec6
(cherry picked from commit 19c5d7e77e)

tripleo-container-manage: search container configs recursively

If the user doesn't override the default value for
tripleo_container_manage_config, which is set to
'/var/lib/tripleo-config/', to something deeper, we want Ansible to
recursively search for the container config until it finds it.

Change-Id: I7d23fec91ffb813f0ab6f11b85d811ef3897f9e0
(cherry picked from commit 205f7c9b0c)

Idempotency for podman_container

Introduce partial idepmpotency for running podman_container

- Replace all options by their defaults it they're not set
- Force lower case on all podman inspection data
- Add a class with methods for every parameter to check its value
and compare it with module argument
- Add check_mode support and podman_actions

Change-Id: I1ae93dff1e10a1a696bb171996a75a0db6c34fa3
(cherry picked from commit 1212544a28)

Fix case where there're no effective caps

When no effective caps in container, it should be list, not None
Change-Id: I5412edc844ad43223c4b3bda35662a7f7ee43f3a

(cherry picked from commit 3a6690c904)

Add idempotency for networks parameter in podman_container

Change-Id: Ib7235d1cb64ad0f42e7a0201008536e1a35bd696
(cherry picked from commit 25f8abbc5a)

use version parsing from distutils

use LooseVersion for version comparisons

Change-Id: I609920a96c725c49f1623f60f8295d89ae4f3141
(cherry picked from commit 8c83219fbb)

Improve idempotency for podman containers module

Fix some pep8 issues
Change-Id: If4233e57edeec10ccac965d61c78f30688cd5531

(cherry picked from commit 0609c16d10)

Improve idempotency for podman container module

Strip all registry values from "image" parameter in input.
Add this case to test.

Change-Id: I78656e48cf85a1a39f873ee40193765eecf02c56
(cherry picked from commit 87d9e9d9a6)
This commit is contained in:
Emilien Macchi 2019-10-02 12:01:28 -04:00
parent 655f7e027f
commit ec4351c566
55 changed files with 3462 additions and 738 deletions

4
.gitignore vendored
View File

@ -11,6 +11,10 @@ cover
nosetests.xml
.testrepository
.venv
.stestr
tripleo_ansible.egg-info/
__pycache__
build
# Editors
*~

3
.stestr.conf Normal file
View File

@ -0,0 +1,3 @@
[DEFAULT]
test_path=${TEST_PATH:-./tripleo_ansible/tests/}
top_dir=./

1
ansible-requirements.txt Normal file
View File

@ -0,0 +1 @@
ansible>=2.8

View File

@ -19,7 +19,9 @@ git [platform:rpm]
libffi-devel [platform:rpm]
openssl-devel [platform:rpm]
python-devel [platform:rpm !platform:rhel-8 !platform:centos-8]
python3-devel [platform:rpm !platform:rhel-7 !platform:centos-7]
python3-devel [platform:rpm !platform:rhel-7 !platform:centos-7]
PyYAML [platform:rpm !platform:rhel-8 !platform:centos-8]
python3-pyyaml [platform:rpm !platform:rhel-7 !platform:centos-7]
python2-dnf [platform:fedora]
# For SELinux

View File

@ -0,0 +1,6 @@
===============================
Role - tripleo-container-manage
===============================
.. ansibleautoplugin::
:role: tripleo_ansible/roles/tripleo-container-manage

View File

@ -1,2 +1,5 @@
pre-commit # MIT
netaddr # BSD
mock>=2.0.0 # BSD
stestr>=2.0.0 # Apache-2.0
oslotest>=3.2.0 # Apache-2.0

16
tox.ini
View File

@ -26,8 +26,13 @@ setenv =
PYTHONWARNINGS=ignore:DEPRECATION::pip._internal.cli.base_command,ignore::UserWarning
PIP_DISABLE_PIP_VERSION_CHECK=1
sitepackages = True
deps = -r {toxinidir}/test-requirements.txt
whitelist_externals = bash
deps =
-r {toxinidir}/test-requirements.txt
-r {toxinidir}/ansible-requirements.txt
commands = stestr run {posargs}
whitelist_externals =
bash
tox
[testenv:bindep]
# Do not install any requirements. We want this to be fast and work even if
@ -123,3 +128,10 @@ commands =
echo -e '\n\nNo molecule tests have been executed\nSee https://docs.openstack.org/tripleo-ansible/latest/contributing.html#local-testing-of-new-roles\n\n'; \
fi"
{[testenv:linters]commands}
[testenv:modules]
deps=
{[testenv:linters]deps}
commands =
bash -c "cd {toxinidir}/tripleo_ansible/ansible_plugins/tests; molecule test --all;"
{[testenv:linters]commands}

View File

@ -0,0 +1,227 @@
#!/usr/bin/env python
# Copyright 2019 Red Hat, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import ast
import json
import six
from collections import OrderedDict
from operator import itemgetter
# cmp() doesn't exist on python3
if six.PY3:
def cmp(a, b):
return 0 if a == b else 1
class FilterModule(object):
def filters(self):
return {
'singledict': self.singledict,
'subsort': self.subsort,
'needs_delete': self.needs_delete,
'haskey': self.haskey,
'list_of_keys': self.list_of_keys,
'container_exec_cmd': self.container_exec_cmd
}
def subsort(self, dict_to_sort, attribute, null_value=0):
"""Sort a hash from a sub-element.
This filter will return an dictionary ordered by the attribute
part of each item.
"""
for k, v in dict_to_sort.items():
if attribute not in v:
dict_to_sort[k][attribute] = null_value
data = {}
for d in dict_to_sort.items():
if d[1][attribute] not in data:
data[d[1][attribute]] = []
data[d[1][attribute]].append({d[0]: d[1]})
sorted_list = sorted(
data.items(),
key=lambda x: x[0]
)
ordered_dict = {}
for o, v in sorted_list:
ordered_dict[o] = v
return ordered_dict
def singledict(self, list_to_convert):
"""Generate a single dictionary from a list of dictionaries.
This filter will return a single dictionary from a list of
dictionaries.
"""
return_dict = {}
for i in list_to_convert:
return_dict.update(i)
return return_dict
def needs_delete(self, container_infos, config, config_id):
"""Returns a list of containers which need to be removed.
This filter will check which containers need to be removed for these
reasons: no config_data, updated config_data or container not
part of the global config.
"""
to_delete = []
to_skip = []
installed_containers = []
for c in container_infos:
c_name = c['Name']
installed_containers.append(c_name)
# Don't delete containers not managed by tripleo-ansible
if (c['Config']['Labels'] is None
or c['Config']['Labels'].get(
'managed_by') != 'tripleo_ansible'):
to_skip += [c_name]
continue
# Only remove containers managed in this config_id
if (c['Config']['Labels'] is None
or c['Config']['Labels'].get('config_id') != config_id):
to_skip += [c_name]
continue
# Remove containers with no config_data
# e.g. broken config containers
if (c['Config']['Labels'] is not None
and 'config_data' not in c['Config']['Labels']):
to_delete += [c_name]
continue
# Remove containers managed by tripleo-ansible that aren't in
# config e.g. containers not needed anymore and removed by an
# upgrade. Note: we don't cleanup paunch-managed containers.
if c_name not in config:
to_delete += [c_name]
continue
for c_name, config_data in config.items():
# don't try to remove a container which doesn't exist
if c_name not in installed_containers:
continue
# already tagged to be removed
if c_name in to_delete:
continue
if c_name in to_skip:
continue
# Remove containers managed by tripleo-ansible when config_data
# changed. Since we already cleaned the containers not in config,
# this check needs to be in that loop.
# e.g. new TRIPLEO_CONFIG_HASH during a minor update
try:
c_facts = [c['Config']['Labels']['config_data']
for c in container_infos if c_name == c['Name']]
except KeyError:
continue
# Build c_facts so it can be compared later with config_data
c_facts = ast.literal_eval(c_facts[0]) if (
len(c_facts)) == 1 else dict()
if cmp(c_facts, config_data) != 0:
to_delete += [c_name]
return to_delete
def haskey(self, batched_container_data, attribute, value=None,
reverse=False, any=False):
"""Return container data with a specific config key.
This filter will take a list of dictionaries (batched_container_data)
and will return the dictionnaries which have a certain key given
in parameter with 'attribute'.
If reverse is set to True, the returned list won't contain dictionaries
which have the attribute.
If any is set to True, the returned list will match any value in
the list of values for "value" parameter which has to be a list.
"""
return_list = []
for container in batched_container_data:
for k, v in json.loads(json.dumps(container)).items():
if attribute in v and not reverse:
if value is None:
return_list.append({k: v})
else:
if isinstance(value, list) and any:
if v[attribute] in value:
return_list.append({k: v})
elif any:
raise TypeError("value has to be a list if any is "
"set to True.")
else:
if v[attribute] == value:
return_list.append({k: v})
if attribute not in v and reverse:
return_list.append({k: v})
return return_list
def list_of_keys(self, keys_to_list):
"""Return a list of keys from a list of dictionaries.
This filter takes in input a list of dictionaries and for each of them
it will add the key to list_of_keys and returns it.
"""
list_of_keys = []
for i in keys_to_list:
for k, v in i.items():
list_of_keys.append(k)
return list_of_keys
def list_or_dict_arg(self, data, cmd, key, arg):
"""Utility to build a command and its argument with list or dict data.
The key can be a dictionary or a list, the returned arguments will be
a list where each item is the argument name and the item data.
"""
if key not in data:
return
value = data[key]
if isinstance(value, dict):
for k, v in sorted(value.items()):
if v:
cmd.append('%s=%s=%s' % (arg, k, v))
elif k:
cmd.append('%s=%s' % (arg, k))
elif isinstance(value, list):
for v in value:
if v:
cmd.append('%s=%s' % (arg, v))
def container_exec_cmd(self, data, cli='podman'):
"""Return a list of all the arguments to execute a container exec.
This filter takes in input the container exec data and the cli name
to return the full command in a list of arguments that will be used
by Ansible command module.
"""
cmd = [cli, 'exec']
cmd.append('--user=%s' % data.get('user', 'root'))
if 'privileged' in data:
cmd.append('--privileged=%s' % str(data['privileged']).lower())
self.list_or_dict_arg(data, cmd, 'environment', '--env')
cmd.extend(data['command'])
return cmd

View File

@ -21,9 +21,10 @@ from __future__ import absolute_import, division, print_function
__metaclass__ = type
import json
from distutils.version import LooseVersion
import yaml
from ansible.module_utils.basic import AnsibleModule, env_fallback
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils._text import to_bytes, to_native
ANSIBLE_METADATA = {
@ -101,7 +102,7 @@ options:
(Not available for remote commands) You can also override the default
path of the authentication file by setting the ``REGISTRY_AUTH_FILE``
environment variable. ``export REGISTRY_AUTH_FILE=path``
type: str
type: path
blkio_weight:
description:
- Block IO weight (relative weight) accepts a weight value between 10 and
@ -128,7 +129,22 @@ options:
If the path is not absolute, the path is considered to be relative to
the cgroups path of the init process. Cgroups will be created if they
do not already exist.
type: path
cgroupns:
description:
- Path to cgroups under which the cgroup for the container will be
created.
type: str
cgroups:
description:
- Determines whether the container will create CGroups.
Valid values are enabled and disabled, which the default being enabled.
The disabled option will force the container to not create CGroups,
and thus conflicts with CGroup options cgroupns and cgroup-parent.
type: str
choices:
- default
- disabled
cidfile:
description:
- Write the container ID to the file
@ -154,9 +170,17 @@ options:
description:
- Limit the CPU real-time period in microseconds
type: int
cpu_rt_period:
description:
- Limit the CPU real-time period in microseconds.
Limit the container's Real Time CPU usage. This flag tell the kernel to
restrict the container's Real Time CPU usage to the period you specify.
type: int
cpu_rt_runtime:
description:
- Limit the CPU real-time runtime in microseconds
- Limit the CPU real-time runtime in microseconds.
This flag tells the kernel to limit the amount of time in a given CPU
period Real Time tasks may consume.
type: int
cpu_shares:
description:
@ -180,6 +204,11 @@ options:
- Run container in detach mode
type: bool
default: True
debug:
description:
- Return additional information which can be helpful for investigations.
type: bool
default: False
detach_keys:
description:
- Override the key sequence for detaching a container. Format is a single
@ -190,27 +219,28 @@ options:
- Add a host device to the container.
The format is <device-on-host>[:<device-on-container>][:<permissions>]
(e.g. device /dev/sdc:/dev/xvdc:rwm)
type: str
type: list
elements: str
device_read_bps:
description:
- Limit read rate (bytes per second) from a device
(e.g. device-read-bps /dev/sda:1mb)
type: str
type: list
device_read_iops:
description:
- Limit read rate (IO per second) from a device
(e.g. device-read-iops /dev/sda:1000)
type: str
type: list
device_write_bps:
description:
- Limit write rate (bytes per second) to a device
(e.g. device-write-bps /dev/sda:1mb)
type: str
type: list
device_write_iops:
description:
- Limit write rate (IO per second) to a device
(e.g. device-write-iops /dev/sda:1000)
type: str
type: list
dns:
description:
- Set custom DNS servers
@ -240,6 +270,11 @@ options:
description:
- Read in a line delimited file of environment variables
type: path
env_host:
description:
- Use all current host environment variables in container.
Defaults to false.
type: bool
etc_hosts:
description:
- Dict of host-to-IP mappings, where each host name is a key in the
@ -271,7 +306,7 @@ options:
group_add:
description:
- Add additional groups to run as
type: str
type: list
healthcheck:
description:
- Set or alter a healthcheck command for a container.
@ -356,6 +391,7 @@ options:
description:
- Kernel memory limit
(format <number>[<unit>], where unit = b, k, m or g)
Note - idempotency is supported for integers only.
type: str
label:
description:
@ -385,10 +421,12 @@ options:
memory:
description:
- Memory limit (format 10k, where unit = b, k, m or g)
Note - idempotency is supported for integers only.
type: str
memory_reservation:
description:
- Memory soft limit (format 100m, where unit = b, k, m or g)
Note - idempotency is supported for integers only.
type: str
memory_swap:
description:
@ -396,6 +434,7 @@ options:
(--memory) flag.
The swap LIMIT should always be larger than -m (--memory) value.
By default, the swap LIMIT will be set to double the value of --memory
Note - idempotency is supported for integers only.
type: str
memory_swappiness:
description:
@ -419,7 +458,8 @@ options:
* ns:<path> path to a network namespace to join
* slirp4netns use slirp4netns to create a user network stack.
This is the default for rootless containers
type: str
type: list
elements: str
aliases:
- net
no_hosts:
@ -512,7 +552,8 @@ options:
security_opt:
description:
- Security Options. For example security_opt "seccomp=unconfined"
type: str
type: list
elements: str
shm_size:
description:
- Size of /dev/shm. The format is <number><unit>. number must be greater
@ -530,7 +571,7 @@ options:
stop_signal:
description:
- Signal to stop a container. Default is SIGTERM.
type: str
type: int
stop_timeout:
description:
- Timeout (in seconds) to stop a container. Default is 10.
@ -813,9 +854,12 @@ class PodmanModuleParams:
params {dict} -- dictionary of module parameters
"""
def __init__(self, action, params):
def __init__(self, action, params, podman_version, module):
self.params = params
self.action = action
self.podman_version = podman_version
self.module = module
def construct_command_from_params(self):
"""Create a podman command from given module parameters.
@ -828,8 +872,8 @@ class PodmanModuleParams:
if self.action in ['create', 'run']:
cmd = [self.action, '--name', self.params['name']]
all_param_methods = [func for func in dir(self)
if callable(getattr(self, func)) and
func.startswith("addparam")]
if callable(getattr(self, func))
and func.startswith("addparam")]
params_set = (i for i in self.params if self.params[i] is not None)
for param in params_set:
func_name = "_".join(["addparam", param])
@ -853,19 +897,26 @@ class PodmanModuleParams:
cmd = ['rm', '-f', self.params['name']]
return [to_bytes(i, errors='surrogate_or_strict') for i in cmd]
def addparam_detach(self, c):
return c + ['--detach=%s' % self.params['detach']]
def addparam_etc_hosts(self, c):
for host_ip in self.params['etc_hosts'].items():
c += ['--add-host', ':'.join(host_ip)]
return c
def check_version(self, param, minv=None, maxv=None):
if minv and LooseVersion(minv) > LooseVersion(
self.podman_version):
self.module.fail_json(msg="Parameter %s is supported from podman "
"version %s only! Current version is %s" % (
param, minv, self.podman_version))
if maxv and LooseVersion(maxv) < LooseVersion(
self.podman_version):
self.module.fail_json(msg="Parameter %s is supported till podman "
"version %s only! Current version is %s" % (
param, minv, self.podman_version))
def addparam_annotation(self, c):
for annotate in self.params['annotation'].items():
c += ['--annotation', '='.join(annotate)]
return c
def addparam_authfile(self, c):
return c + ['--authfile', self.params['authfile']]
def addparam_blkio_weight(self, c):
return c + ['--blkio-weight', self.params['blkio_weight']]
@ -884,6 +935,14 @@ class PodmanModuleParams:
c += ['--cap-drop', cap_drop]
return c
def addparam_cgroups(self, c):
self.check_version('--cgroups', minv='1.6.0')
return c + ['--cgroups=%s' % self.params['cgroups']]
def addparam_cgroupns(self, c):
self.check_version('--cgroupns', minv='1.6.2')
return c + ['--cgroupns=%s' % self.params['cgroupns']]
def addparam_cgroup_parent(self, c):
return c + ['--cgroup-parent', self.params['cgroup_parent']]
@ -896,6 +955,9 @@ class PodmanModuleParams:
def addparam_cpu_period(self, c):
return c + ['--cpu-period', self.params['cpu_period']]
def addparam_cpu_rt_period(self, c):
return c + ['--cpu-rt-period', self.params['cpu_rt_period']]
def addparam_cpu_rt_runtime(self, c):
return c + ['--cpu-rt-runtime', self.params['cpu_rt_runtime']]
@ -911,23 +973,36 @@ class PodmanModuleParams:
def addparam_cpuset_mems(self, c):
return c + ['--cpuset-mems', self.params['cpuset_mems']]
def addparam_detach(self, c):
return c + ['--detach=%s' % self.params['detach']]
def addparam_detach_keys(self, c):
return c + ['--detach-keys', self.params['detach_keys']]
def addparam_device(self, c):
return c + ['--device', self.params['device']]
for dev in self.params['device']:
c += ['--device', dev]
return c
def addparam_device_read_bps(self, c):
return c + ['--device-read-bps', self.params['device_read_bps']]
for dev in self.params['device_read_bps']:
c += ['--device-read-bps', dev]
return c
def addparam_device_read_iops(self, c):
return c + ['--device-read-iops', self.params['device_read_iops']]
for dev in self.params['device_read_iops']:
c += ['--device-read-iops', dev]
return c
def addparam_device_write_bps(self, c):
return c + ['--device-write-bps', self.params['device_write_bps']]
for dev in self.params['device_write_bps']:
c += ['--device-write-bps', dev]
return c
def addparam_device_write_iops(self, c):
return c + ['--device-write-iops', self.params['device_write_iops']]
for dev in self.params['device_write_iops']:
c += ['--device-write-iops', dev]
return c
def addparam_dns(self, c):
return c + ['--dns', ','.join(self.params['dns'])]
@ -951,6 +1026,15 @@ class PodmanModuleParams:
def addparam_env_file(self, c):
return c + ['--env-file', self.params['env_file']]
def addparam_env_host(self, c):
self.check_version('--env-host', minv='1.5.0')
return c + ['--env-host=%s' % self.params['env_host']]
def addparam_etc_hosts(self, c):
for host_ip in self.params['etc_hosts'].items():
c += ['--add-host', ':'.join(host_ip)]
return c
def addparam_expose(self, c):
for exp in self.params['expose']:
c += ['--expose', exp]
@ -960,7 +1044,9 @@ class PodmanModuleParams:
return c + ['--gidmap', self.params['gidmap']]
def addparam_group_add(self, c):
return c + ['--group-add', self.params['group_add']]
for g in self.params['group_add']:
c += ['--group-add', g]
return c
def addparam_healthcheck(self, c):
return c + ['--healthcheck', self.params['healthcheck']]
@ -1010,7 +1096,8 @@ class PodmanModuleParams:
def addparam_label(self, c):
for label in self.params['label'].items():
c += ['--label', '='.join(label)]
c += ['--label', b'='.join([to_bytes(l, errors='surrogate_or_strict')
for l in label])]
return c
def addparam_label_file(self, c):
@ -1038,7 +1125,7 @@ class PodmanModuleParams:
return c + ['--mount', self.params['mount']]
def addparam_network(self, c):
return c + ['--network', self.params['network']]
return c + ['--network', ",".join(self.params['network'])]
def addparam_no_hosts(self, c):
return c + ['--no-hosts=%s' % self.params['no_hosts']]
@ -1085,7 +1172,9 @@ class PodmanModuleParams:
return c + ['--rootfs=%s' % self.params['rootfs']]
def addparam_security_opt(self, c):
return c + ['--security-opt', self.params['security_opt']]
for secopt in self.params['security_opt']:
c += ['--security-opt', secopt]
return c
def addparam_shm_size(self, c):
return c + ['--shm-size', self.params['shm_size']]
@ -1161,6 +1250,436 @@ class PodmanModuleParams:
return c + self.params['cmd_args']
class PodmanDefaults:
def __init__(self, module, podman_version):
self.module = module
self.version = podman_version
self.defaults = {
"blkio_weight": 0,
"cgroups": "default",
"cgroup_parent": "",
"cidfile": "",
"cpus": 0.0,
"cpu_shares": 0,
"cpu_quota": 0,
"cpu_period": 0,
"cpu_rt_runtime": 0,
"cpu_rt_period": 0,
"cpuset_cpus": "",
"cpuset_mems": "",
"detach": True,
"device": [],
"env_host": False,
"etc_hosts": {},
"group_add": [],
"ipc": "",
"kernelmemory": "0",
"log_driver": "k8s-file",
"memory": "0",
"memory_swap": "0",
"memory_reservation": "0",
# "memory_swappiness": -1,
"no_hosts": False,
# libpod issue with networks in inspection
"network": ["default"],
"oom_score_adj": 0,
"pid": "",
"privileged": False,
"rm": False,
"security_opt": [],
"stop_signal": 15,
"tty": False,
"user": "",
"uts": "",
"volume": [],
"workdir": "/",
}
def default_dict(self):
# make here any changes to self.defaults related to podman version
return self.defaults
class PodmanContainerDiff:
def __init__(self, module, info, podman_version):
self.module = module
self.version = podman_version
self.default_dict = None
self.info = yaml.safe_load(json.dumps(info).lower())
self.params = self.defaultize()
self.diff = {'before': {}, 'after': {}}
self.non_idempotent = {
'env_file',
'env_host',
"ulimit", # Defaults depend on user and platform, impossible to guess
}
def defaultize(self):
params_with_defaults = {}
self.default_dict = PodmanDefaults(
self.module, self.version).default_dict()
for p in self.module.params:
if self.module.params[p] is None and p in self.default_dict:
params_with_defaults[p] = self.default_dict[p]
else:
params_with_defaults[p] = self.module.params[p]
return params_with_defaults
def _diff_update_and_compare(self, param_name, before, after):
if before != after:
self.diff['before'].update({param_name: before})
self.diff['after'].update({param_name: after})
return True
return False
def diffparam_annotation(self):
before = self.info['config']['annotations'] or {}
after = before.copy()
if self.module.params['annotation'] is not None:
after.update(self.params['annotation'])
return self._diff_update_and_compare('annotation', before, after)
def diffparam_env_host(self):
# It's impossible to get from inspest, recreate it if not default
before = False
after = self.params['env_host']
return self._diff_update_and_compare('env_host', before, after)
def diffparam_blkio_weight(self):
before = self.info['hostconfig']['blkioweight']
after = self.params['blkio_weight']
return self._diff_update_and_compare('blkio_weight', before, after)
def diffparam_blkio_weight_device(self):
before = self.info['hostconfig']['blkioweightdevice']
if before == [] and self.module.params['blkio_weight_device'] is None:
after = []
else:
after = self.params['blkio_weight_device']
return self._diff_update_and_compare('blkio_weight_device', before, after)
def diffparam_cap_add(self):
before = self.info['effectivecaps'] or []
after = []
if self.module.params['cap_add'] is not None:
after += ["cap_" + i.lower()
for i in self.module.params['cap_add']]
after += before
before, after = sorted(list(set(before))), sorted(list(set(after)))
return self._diff_update_and_compare('cap_add', before, after)
def diffparam_cap_drop(self):
before = self.info['effectivecaps'] or []
after = before[:]
if self.module.params['cap_drop'] is not None:
for c in ["cap_" + i.lower() for i in self.module.params['cap_drop']]:
if c in after:
after.remove(c)
before, after = sorted(list(set(before))), sorted(list(set(after)))
return self._diff_update_and_compare('cap_drop', before, after)
def diffparam_cgroup_parent(self):
before = self.info['hostconfig']['cgroupparent']
after = self.params['cgroup_parent']
return self._diff_update_and_compare('cgroup_parent', before, after)
def diffparam_cgroups(self):
# Cgroups output is not supported in all versions
if 'cgroups' in self.info['hostconfig']:
before = self.info['hostconfig']['cgroups']
after = self.params['cgroups']
return self._diff_update_and_compare('cgroups', before, after)
return False
def diffparam_cidfile(self):
before = self.info['hostconfig']['containeridfile']
after = self.params['cidfile']
return self._diff_update_and_compare('cidfile', before, after)
def diffparam_command(self):
before = self.info['config']['cmd']
after = self.params['command']
if isinstance(after, str):
after = [i.lower() for i in after.split()]
elif isinstance(after, list):
after = [i.lower() for i in after]
return self._diff_update_and_compare('command', before, after)
def diffparam_conmon_pidfile(self):
before = self.info['conmonpidfile']
if self.module.params['conmon_pidfile'] is None:
after = before
else:
after = self.params['conmon_pidfile']
return self._diff_update_and_compare('conmon_pidfile', before, after)
def diffparam_cpu_period(self):
before = self.info['hostconfig']['cpuperiod']
after = self.params['cpu_period']
return self._diff_update_and_compare('cpu_period', before, after)
def diffparam_cpu_rt_period(self):
before = self.info['hostconfig']['cpurealtimeperiod']
after = self.params['cpu_rt_period']
return self._diff_update_and_compare('cpu_rt_period', before, after)
def diffparam_cpu_rt_runtime(self):
before = self.info['hostconfig']['cpurealtimeruntime']
after = self.params['cpu_rt_runtime']
return self._diff_update_and_compare('cpu_rt_runtime', before, after)
def diffparam_cpu_shares(self):
before = self.info['hostconfig']['cpushares']
after = self.params['cpu_shares']
return self._diff_update_and_compare('cpu_shares', before, after)
def diffparam_cpus(self):
before = int(self.info['hostconfig']['nanocpus']) / 1000000000
after = self.params['cpus']
return self._diff_update_and_compare('cpus', before, after)
def diffparam_cpuset_cpus(self):
before = self.info['hostconfig']['cpusetcpus']
after = self.params['cpuset_cpus']
return self._diff_update_and_compare('cpuset_cpus', before, after)
def diffparam_cpuset_mems(self):
before = self.info['hostconfig']['cpusetmems']
after = self.params['cpuset_mems']
return self._diff_update_and_compare('cpuset_mems', before, after)
def diffparam_device(self):
before = [":".join([i['pathonhost'], i['pathincontainer']])
for i in self.info['hostconfig']['devices']]
after = [":".join(i.split(":")[:2]) for i in self.params['device']]
before, after = sorted(list(set(before))), sorted(list(set(after)))
return self._diff_update_and_compare('devices', before, after)
def diffparam_device_read_bps(self):
before = self.info['hostconfig']['blkiodevicereadbps'] or []
before = ["%s:%s" % (i['path'], i['rate']) for i in before]
after = self.params['device_read_bps'] or []
before, after = sorted(list(set(before))), sorted(list(set(after)))
return self._diff_update_and_compare('device_read_bps', before, after)
def diffparam_device_read_iops(self):
before = self.info['hostconfig']['blkiodevicereadiops'] or []
before = ["%s:%s" % (i['path'], i['rate']) for i in before]
after = self.params['device_read_iops'] or []
before, after = sorted(list(set(before))), sorted(list(set(after)))
return self._diff_update_and_compare('device_read_iops', before, after)
def diffparam_device_write_bps(self):
before = self.info['hostconfig']['blkiodevicewritebps'] or []
before = ["%s:%s" % (i['path'], i['rate']) for i in before]
after = self.params['device_write_bps'] or []
before, after = sorted(list(set(before))), sorted(list(set(after)))
return self._diff_update_and_compare('device_write_bps', before, after)
def diffparam_device_write_iops(self):
before = self.info['hostconfig']['blkiodevicewriteiops'] or []
before = ["%s:%s" % (i['path'], i['rate']) for i in before]
after = self.params['device_write_iops'] or []
before, after = sorted(list(set(before))), sorted(list(set(after)))
return self._diff_update_and_compare('device_write_iops', before, after)
# Limited idempotency, it can't guess default values
def diffparam_env(self):
env_before = self.info['config']['env'] or {}
before = {i.split("=")[0]: i.split("=")[1] for i in env_before}
after = before.copy()
if self.params['env']:
after.update({
str(k).lower(): str(v).lower()
for k, v in self.params['env'].items()
})
return self._diff_update_and_compare('env', before, after)
def diffparam_etc_hosts(self):
if self.info['hostconfig']['extrahosts']:
before = dict([i.split(":") for i in self.info['hostconfig']['extrahosts']])
else:
before = {}
after = self.params['etc_hosts']
return self._diff_update_and_compare('etc_hosts', before, after)
def diffparam_group_add(self):
before = self.info['hostconfig']['groupadd']
after = self.params['group_add']
return self._diff_update_and_compare('group_add', before, after)
# Because of hostname is random generated, this parameter has partial idempotency only.
def diffparam_hostname(self):
before = self.info['config']['hostname']
after = self.params['hostname'] or before
return self._diff_update_and_compare('hostname', before, after)
def diffparam_image(self):
before = self.info['config']['image'].replace(
"docker.io/library/", "").replace(
"docker.io/", "").replace(
":latest", "")
after = self.params['image'].replace(
"docker.io/library/", "").replace(
"docker.io/", "").replace(
":latest", "")
return self._diff_update_and_compare('image', before, after)
def diffparam_ipc(self):
before = self.info['hostconfig']['ipcmode']
after = self.params['ipc']
return self._diff_update_and_compare('ipc', before, after)
def diffparam_label(self):
before = self.info['config']['labels'] or {}
after = before.copy()
if self.params['label']:
after.update({
str(k).lower(): str(v).lower()
for k, v in self.params['label'].items()
})
return self._diff_update_and_compare('label', before, after)
def diffparam_log_driver(self):
before = self.info['hostconfig']['logconfig']['type']
after = self.params['log_driver']
return self._diff_update_and_compare('log_driver', before, after)
# Parameter has limited idempotency, unable to guess the default log_path
def diffparam_log_opt(self):
before = self.info['logpath']
if self.module.params['log_opt'] in [None, '']:
after = before
else:
after = self.params['log_opt'].split("=")[1]
return self._diff_update_and_compare('log_opt', before, after)
def diffparam_memory(self):
before = str(self.info['hostconfig']['memory'])
after = self.params['memory']
return self._diff_update_and_compare('memory', before, after)
def diffparam_memory_swap(self):
# By default it's twice memory parameter
before = str(self.info['hostconfig']['memoryswap'])
after = self.params['memory_swap']
if (self.module.params['memory_swap'] is None
and self.params['memory'] != 0
and self.params['memory'].isdigit()):
after = str(int(self.params['memory']) * 2)
return self._diff_update_and_compare('memory_swap', before, after)
def diffparam_memory_reservation(self):
before = str(self.info['hostconfig']['memoryreservation'])
after = self.params['memory_reservation']
return self._diff_update_and_compare('memory_reservation', before, after)
def diffparam_network(self):
before = [self.info['hostconfig']['networkmode']]
after = self.params['network']
return self._diff_update_and_compare('network', before, after)
def diffparam_no_hosts(self):
before = not bool(self.info['hostspath'])
after = self.params['no_hosts']
if self.params['network'] == ['none']:
after = True
return self._diff_update_and_compare('no_hosts', before, after)
def diffparam_oom_score_adj(self):
before = self.info['hostconfig']['oomscoreadj']
after = self.params['oom_score_adj']
return self._diff_update_and_compare('oom_score_adj', before, after)
def diffparam_privileged(self):
before = self.info['hostconfig']['privileged']
after = self.params['privileged']
return self._diff_update_and_compare('privileged', before, after)
def diffparam_pid(self):
before = self.info['hostconfig']['pidmode']
after = self.params['pid']
return self._diff_update_and_compare('pid', before, after)
def diffparam_rm(self):
before = self.info['hostconfig']['autoremove']
after = self.params['rm']
return self._diff_update_and_compare('rm', before, after)
def diffparam_security_opt(self):
before = self.info['hostconfig']['securityopt']
after = self.params['security_opt']
before, after = sorted(list(set(before))), sorted(list(set(after)))
return self._diff_update_and_compare('security_opt', before, after)
def diffparam_stop_signal(self):
before = self.info['config']['stopsignal']
after = self.params['stop_signal']
return self._diff_update_and_compare('stop_signal', before, after)
def diffparam_tty(self):
before = self.info['config']['tty']
after = self.params['tty']
return self._diff_update_and_compare('tty', before, after)
def diffparam_user(self):
before = self.info['config']['user']
if self.module.params['user'] is None and before:
after = before
else:
after = self.params['user']
return self._diff_update_and_compare('user', before, after)
def diffparam_uts(self):
before = self.info['hostconfig']['utsmode']
after = self.params['uts']
return self._diff_update_and_compare('uts', before, after)
def diffparam_volume(self):
before = self.info['mounts']
if before:
volumes = []
for m in before:
if m['type'] == 'volume':
volumes.append([m['name'], m['destination']])
else:
volumes.append([m['source'], m['destination']])
before = [":".join(v) for v in volumes]
# Ignore volumes option for idempotency
after = [":".join(v.split(":")[:2]) for v in self.params['volume']]
before, after = sorted(list(set(before))), sorted(list(set(after)))
return self._diff_update_and_compare('volume', before, after)
def diffparam_volumes_from(self):
before = self.info['hostconfig']['volumesfrom'] or []
after = self.params['volumes_from'] or []
return self._diff_update_and_compare('volumes_from', before, after)
def diffparam_workdir(self):
before = self.info['config']['workingdir']
after = self.params['workdir']
return self._diff_update_and_compare('workdir', before, after)
def is_different(self):
diff_func_list = [func for func in dir(self)
if callable(getattr(self, func)) and func.startswith(
"diffparam")]
fail_fast = not bool(self.module._diff)
different = False
for func_name in diff_func_list:
dff_func = getattr(self, func_name)
if dff_func():
if fail_fast:
return True
else:
different = True
# Check non idempotent parameters
for p in self.non_idempotent:
if self.module.params[p] is not None and self.module.params[p] not in [{}, [], '']:
different = True
return different
def ensure_image_exists(module, image):
"""If image is passed, ensure it exists, if not - pull it or fail.
@ -1205,6 +1724,9 @@ class PodmanContainer:
self.name = name
self.stdout, self.stderr = '', ''
self.info = self.get_info()
self.version = self._get_podman_version()
self.diff = {}
self.actions = []
@property
def exists(self):
@ -1214,9 +1736,17 @@ class PodmanContainer:
@property
def different(self):
"""Check if container is different."""
# TODO(sshnaidm): implement difference calculation between input vars
# and current container to understand if we need to recreate it
return True
diffcheck = PodmanContainerDiff(self.module, self.info, self.version)
is_different = diffcheck.is_different()
diffs = diffcheck.diff
if self.module._diff and is_different and diffs['before'] and diffs['after']:
self.diff['before'] = "\n".join(
["%s - %s" % (k, v) for k, v in sorted(
diffs['before'].items())]) + "\n"
self.diff['after'] = "\n".join(
["%s - %s" % (k, v) for k, v in sorted(
diffs['after'].items())]) + "\n"
return is_different
@property
def running(self):
@ -1234,6 +1764,13 @@ class PodmanContainer:
[self.module.params['executable'], b'container', b'inspect', self.name])
return json.loads(out)[0] if rc == 0 else {}
def _get_podman_version(self):
rc, out, err = self.module.run_command(
[self.module.params['executable'], b'--version'])
if rc != 0 or not out or "version" not in out:
self.module.fail_json(msg="%s run failed!" % self.module.params['executable'])
return out.split("version")[1].strip()
def _perform_action(self, action):
"""Perform action with container.
@ -1241,19 +1778,25 @@ class PodmanContainer:
action {str} -- action to perform - start, create, stop, run,
delete
"""
b_command = PodmanModuleParams(action, self.module.params
b_command = PodmanModuleParams(action,
self.module.params,
self.version,
self.module,
).construct_command_from_params()
self.module.log("PODMAN-CONTAINER-DEBUG: " +
"%s" % " ".join([to_native(i) for i in b_command]))
rc, out, err = self.module.run_command(
[self.module.params['executable'], b'container'] + b_command,
expand_user_and_vars=False)
self.stdout = out
self.stderr = err
if rc != 0:
self.module.fail_json(
msg="Can't %s container %s" % (action, self.name),
stdout=out, stderr=err)
full_cmd = " ".join([self.module.params['executable']]
+ [to_native(i) for i in b_command])
self.module.log("PODMAN-CONTAINER-DEBUG: %s" % full_cmd)
self.actions.append(full_cmd)
if not self.module.check_mode:
rc, out, err = self.module.run_command(
[self.module.params['executable'], b'container'] + b_command,
expand_user_and_vars=False)
self.stdout = out
self.stderr = err
if rc != 0:
self.module.fail_json(
msg="Can't %s container %s" % (action, self.name),
stdout=out, stderr=err)
def run(self):
"""Run the container."""
@ -1326,11 +1869,15 @@ class PodmanManager:
changed {bool} -- whether any action was performed
(default: {True})
"""
facts = self.container.get_info()
facts = self.container.get_info() if changed else self.container.info
out, err = self.container.stdout, self.container.stderr
self.results.update({'changed': changed, 'container': facts,
'ansible_facts': {'podman_container': facts}},
stdout=out, stderr=err)
'podman_actions': self.container.actions},
stdout=out, stderr=err)
if self.container.diff:
self.results.update({'diff': self.container.diff})
if self.module.params['debug']:
self.results.update({'podman_version': self.container.version})
self.module.exit_json(**self.results)
def make_started(self):
@ -1347,7 +1894,7 @@ class PodmanManager:
self.results['actions'].append('restarted %s' %
self.container.name)
self.update_container_result()
self.module.exit_json(**self.results)
self.update_container_result(changed=False)
elif not self.container.exists:
self.container.run()
self.results['actions'].append('started %s' % self.container.name)
@ -1365,7 +1912,7 @@ class PodmanManager:
def make_stopped(self):
"""Run actions if desired state is 'stopped'."""
if not self.container.exists and not self.image:
self.module.fail_json(msg='Cannot create container when image' +
self.module.fail_json(msg='Cannot create container when image'
' is not specified!')
if not self.container.exists:
self.container.create()
@ -1387,7 +1934,7 @@ class PodmanManager:
self.results['actions'].append('deleted %s' % self.container.name)
self.results.update({'changed': True})
self.results.update({'container': {},
'ansible_facts': {'podman_container': {}}})
'podman_actions': self.container.actions})
self.module.exit_json(**self.results)
def execute(self):
@ -1400,7 +1947,7 @@ class PodmanManager:
}
process_action = states_map[self.state]
process_action()
self.module.fail_json(msg="Unexpected logic error happened, " +
self.module.fail_json(msg="Unexpected logic error happened, "
"please contact maintainers ASAP!")
@ -1411,6 +1958,7 @@ def main():
['no_hosts', 'etc_hosts'],
),
supports_check_mode=True,
)
# work on input vars
if module.params['state'] in ['started', 'present'] and \

View File

@ -13,54 +13,43 @@
# License for the specific language governing permissions and limitations
# under the License.
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import json
import yaml
from ansible.module_utils.basic import AnsibleModule
import ansible.module_utils.six as six
six.add_metaclass(type)
ANSIBLE_METADATA = {
'metadata_version': '1.0',
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'
}
DOCUMENTATION = """
---
module: podman_container_info
author:
- Sagi Shnaidman (@sshnaidm)
- Emilien Macchi (@EmilienM)
version_added: '2.9'
version_added: '2.10'
short_description: Gather facts about containers using podman
notes:
- Podman may required elevated privileges in order to run properly.
- Podman may require elevated privileges in order to run properly.
description:
- Gather facts about containers using C(podman)
requirements:
- "Podman installed on host"
options:
executable:
description:
- Path to C(podman) executable if it is not in the C($PATH) on the
machine running C(podman)
default: 'podman'
type: str
name:
description:
- List of tags or UID to gather facts about. If no name is given
return facts about all containers.
type: list
elements: str
name:
description:
- List of container names to gather facts about. If no name is given
return facts about all containers.
type: list
elements: str
executable:
description:
- Path to C(podman) executable if it is not in the C($PATH) on the
machine running C(podman)
default: 'podman'
type: str
"""
EXAMPLES = """
- name: Gather facts for all containers
podman_container_info:
@ -75,273 +64,274 @@ EXAMPLES = """
- redis
- web1
"""
RETURN = """
containers:
description: Facts from all or specificed containers
returned: always
type: dict
type: list
sample: [
{
"Id": "c5c39f9b80a6ea2ad665aa9946435934e478a0c5322da835f3883872f",
"Created": "2019-10-01T12:51:00.233106443Z",
"Path": "dumb-init",
"Args": [
"--single-child",
"--",
"kolla_start"
],
"State": {
"OciVersion": "1.0.1-dev",
"Status": "configured",
"Running": false,
"Paused": false,
"Restarting": false,
"OOMKilled": false,
"Dead": false,
"Pid": 0,
"ExitCode": 0,
"Error": "",
"StartedAt": "0001-01-01T00:00:00Z",
"FinishedAt": "0001-01-01T00:00:00Z",
"Healthcheck": {
"Status": "",
"FailingStreak": 0,
"Log": null
}
},
"Image": "0e267acda67d0ebd643e900d820a91b961d859743039e620191ca1",
"ImageName": "docker.io/tripleomaster/centos-haproxy:latest",
"Rootfs": "",
"Pod": "",
"ResolvConfPath": "",
"HostnamePath": "",
"HostsPath": "",
"OCIRuntime": "runc",
"Name": "haproxy",
"RestartCount": 0,
"Driver": "overlay",
"MountLabel": "system_u:object_r:svirt_sandbox_file_t:s0:c78,c866",
"ProcessLabel": "system_u:system_r:svirt_lxc_net_t:s0:c785,c866",
"AppArmorProfile": "",
"EffectiveCaps": [
"CAP_CHOWN",
"CAP_DAC_OVERRIDE",
"CAP_FSETID",
"CAP_FOWNER",
"CAP_MKNOD",
"CAP_NET_RAW",
"CAP_SETGID",
"CAP_SETUID",
"CAP_SETFCAP",
"CAP_SETPCAP",
"CAP_NET_BIND_SERVICE",
"CAP_SYS_CHROOT",
"CAP_KILL",
"CAP_AUDIT_WRITE"
],
"BoundingCaps": [
"CAP_CHOWN",
"CAP_DAC_OVERRIDE",
"CAP_FSETID",
"CAP_FOWNER",
"CAP_MKNOD",
"CAP_NET_RAW",
"CAP_SETGID",
"CAP_SETUID",
"CAP_SETFCAP",
"CAP_SETPCAP",
"CAP_NET_BIND_SERVICE",
"CAP_SYS_CHROOT",
"CAP_KILL",
"CAP_AUDIT_WRITE"
],
"ExecIDs": [],
"GraphDriver": {
"Name": "overlay",
}
},
"Mounts": [],
"Dependencies": [],
"NetworkSettings": {
"Bridge": "",
"SandboxID": "",
"HairpinMode": false,
"LinkLocalIPv6Address": "",
"LinkLocalIPv6PrefixLen": 0,
"Ports": [],
"SandboxKey": "",
"SecondaryIPAddresses": null,
"SecondaryIPv6Addresses": null,
"EndpointID": "",
"Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"IPAddress": "",
"IPPrefixLen": 0,
"IPv6Gateway": "",
"MacAddress": ""
},
"ExitCommand": [
"/usr/bin/podman",
"--root",
"/var/lib/containers/storage",
"--runroot",
"/var/run/containers/storage",
"--log-level",
"error",
"--cgroup-manager",
"systemd",
"--tmpdir",
"/var/run/libpod",
"--runtime",
"runc",
"--storage-driver",
"overlay",
"--events-backend",
"journald",
"container",
"cleanup",
"c9e813703f9b80a6ea2ad665aa9946435934e478a0c5322da835f3883872f"
],
"Namespace": "",
"IsInfra": false,
"Config": {
"Hostname": "c5c39e813703",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"TERM=xterm",
"HOSTNAME=",
"container=oci",
"KOLLA_INSTALL_METATYPE=rdo",
"KOLLA_BASE_DISTRO=centos",
"KOLLA_INSTALL_TYPE=binary",
"KOLLA_DISTRO_PYTHON_VERSION=2.7",
"KOLLA_BASE_ARCH=x86_64"
],
"Cmd": [
{
"Id": "c5c39f9b80a6ea2ad665aa9946435934e478a0c5322da835f3883872f",
"Created": "2019-10-01T12:51:00.233106443Z",
"Path": "dumb-init",
"Args": [
"--single-child",
"--",
"kolla_start"
],
"Image": "docker.io/tripleomaster/centos-haproxy:latest",
"Volumes": null,
"WorkingDir": "/",
"Entrypoint": "dumb-init --single-child --",
"OnBuild": null,
"Labels": {
"build-date": "20190919",
"kolla_version": "8.1.0",
"name": "haproxy",
"org.label-schema.build-date": "20190801",
"org.label-schema.license": "GPLv2",
"org.label-schema.name": "CentOS Base Image",
"org.label-schema.schema-version": "1.0",
"org.label-schema.vendor": "CentOS"
},
"Annotations": {
"io.kubernetes.cri-o.ContainerType": "sandbox",
"io.kubernetes.cri-o.TTY": "false",
"io.podman.annotations.autoremove": "FALSE",
"io.podman.annotations.init": "FALSE",
"io.podman.annotations.privileged": "FALSE",
"io.podman.annotations.publish-all": "FALSE"
},
"StopSignal": 15
},
"HostConfig": {
"Binds": [],
"ContainerIDFile": "",
"LogConfig": {
"Type": "k8s-file",
"Config": null
},
"NetworkMode": "default",
"PortBindings": {},
"RestartPolicy": {
"Name": "",
"MaximumRetryCount": 0
},
"AutoRemove": false,
"VolumeDriver": "",
"VolumesFrom": null,
"CapAdd": [],
"CapDrop": [],
"Dns": [],
"DnsOptions": [],
"DnsSearch": [],
"ExtraHosts": [],
"GroupAdd": [],
"IpcMode": "",
"Cgroup": "",
"Links": null,
"OomScoreAdj": 0,
"PidMode": "",
"Privileged": false,
"PublishAllPorts": false,
"ReadonlyRootfs": false,
"SecurityOpt": [],
"Tmpfs": {},
"UTSMode": "",
"UsernsMode": "",
"ShmSize": 65536000,
"Runtime": "oci",
"ConsoleSize": [
0,
0
],
"Isolation": "",
"CpuShares": 0,
"Memory": 0,
"NanoCpus": 0,
"CgroupParent": "",
"BlkioWeight": 0,
"BlkioWeightDevice": null,
"BlkioDeviceReadBps": null,
"BlkioDeviceWriteBps": null,
"BlkioDeviceReadIOps": null,
"BlkioDeviceWriteIOps": null,
"CpuPeriod": 0,
"CpuQuota": 0,
"CpuRealtimePeriod": 0,
"CpuRealtimeRuntime": 0,
"CpusetCpus": "",
"CpusetMems": "",
"Devices": [],
"DiskQuota": 0,
"KernelMemory": 0,
"MemoryReservation": 0,
"MemorySwap": 0,
"MemorySwappiness": -1,
"OomKillDisable": false,
"PidsLimit": 0,
"Ulimits": [
{
"Name": "RLIMIT_NOFILE",
"Soft": 1048576,
"Hard": 1048576
},
{
"Name": "RLIMIT_NPROC",
"Soft": 1048576,
"Hard": 1048576
"State": {
"OciVersion": "1.0.1-dev",
"Status": "configured",
"Running": false,
"Paused": false,
"Restarting": false,
"OOMKilled": false,
"Dead": false,
"Pid": 0,
"ExitCode": 0,
"Error": "",
"StartedAt": "0001-01-01T00:00:00Z",
"FinishedAt": "0001-01-01T00:00:00Z",
"Healthcheck": {
"Status": "",
"FailingStreak": 0,
"Log": null
}
},
"Image": "0e267acda67d0ebd643e900d820a91b961d859743039e620191ca1",
"ImageName": "docker.io/tripleomaster/centos-haproxy:latest",
"Rootfs": "",
"Pod": "",
"ResolvConfPath": "",
"HostnamePath": "",
"HostsPath": "",
"OCIRuntime": "runc",
"Name": "haproxy",
"RestartCount": 0,
"Driver": "overlay",
"MountLabel": "system_u:object_r:svirt_sandbox_file_t:s0:c78,c866",
"ProcessLabel": "system_u:system_r:svirt_lxc_net_t:s0:c785,c866",
"AppArmorProfile": "",
"EffectiveCaps": [
"CAP_CHOWN",
"CAP_DAC_OVERRIDE",
"CAP_FSETID",
"CAP_FOWNER",
"CAP_MKNOD",
"CAP_NET_RAW",
"CAP_SETGID",
"CAP_SETUID",
"CAP_SETFCAP",
"CAP_SETPCAP",
"CAP_NET_BIND_SERVICE",
"CAP_SYS_CHROOT",
"CAP_KILL",
"CAP_AUDIT_WRITE"
],
"CpuCount": 0,
"CpuPercent": 0,
"IOMaximumIOps": 0,
"IOMaximumBandwidth": 0
}
}
]
"BoundingCaps": [
"CAP_CHOWN",
"CAP_DAC_OVERRIDE",
"CAP_FSETID",
"CAP_FOWNER",
"CAP_MKNOD",
"CAP_NET_RAW",
"CAP_SETGID",
"CAP_SETUID",
"CAP_SETFCAP",
"CAP_SETPCAP",
"CAP_NET_BIND_SERVICE",
"CAP_SYS_CHROOT",
"CAP_KILL",
"CAP_AUDIT_WRITE"
],
"ExecIDs": [],
"GraphDriver": {
"Name": "overlay"
},
"Mounts": [],
"Dependencies": [],
"NetworkSettings": {
"Bridge": "",
"SandboxID": "",
"HairpinMode": false,
"LinkLocalIPv6Address": "",
"LinkLocalIPv6PrefixLen": 0,
"Ports": [],
"SandboxKey": "",
"SecondaryIPAddresses": null,
"SecondaryIPv6Addresses": null,
"EndpointID": "",
"Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"IPAddress": "",
"IPPrefixLen": 0,
"IPv6Gateway": "",
"MacAddress": ""
},
"ExitCommand": [
"/usr/bin/podman",
"--root",
"/var/lib/containers/storage",
"--runroot",
"/var/run/containers/storage",
"--log-level",
"error",
"--cgroup-manager",
"systemd",
"--tmpdir",
"/var/run/libpod",
"--runtime",
"runc",
"--storage-driver",
"overlay",
"--events-backend",
"journald",
"container",
"cleanup",
"c9e813703f9b80a6ea2ad665aa9946435934e478a0c5322da835f3883872f"
],
"Namespace": "",
"IsInfra": false,
"Config": {
"Hostname": "c5c39e813703",
"Domainname": "",
"User": "",
"AttachStdin": false,
"AttachStdout": false,
"AttachStderr": false,
"Tty": false,
"OpenStdin": false,
"StdinOnce": false,
"Env": [
"PATH=/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"TERM=xterm",
"HOSTNAME=",
"container=oci",
"KOLLA_INSTALL_METATYPE=rdo",
"KOLLA_BASE_DISTRO=centos",
"KOLLA_INSTALL_TYPE=binary",
"KOLLA_DISTRO_PYTHON_VERSION=2.7",
"KOLLA_BASE_ARCH=x86_64"
],
"Cmd": [
"kolla_start"
],
"Image": "docker.io/tripleomaster/centos-haproxy:latest",
"Volumes": null,
"WorkingDir": "/",
"Entrypoint": "dumb-init --single-child --",
"OnBuild": null,
"Labels": {
"build-date": "20190919",
"kolla_version": "8.1.0",
"name": "haproxy",
"org.label-schema.build-date": "20190801",
"org.label-schema.license": "GPLv2",
"org.label-schema.name": "CentOS Base Image",
"org.label-schema.schema-version": "1.0",
"org.label-schema.vendor": "CentOS"
},
"Annotations": {
"io.kubernetes.cri-o.ContainerType": "sandbox",
"io.kubernetes.cri-o.TTY": "false",
"io.podman.annotations.autoremove": "FALSE",
"io.podman.annotations.init": "FALSE",
"io.podman.annotations.privileged": "FALSE",
"io.podman.annotations.publish-all": "FALSE"
},
"StopSignal": 15
},
"HostConfig": {
"Binds": [],
"ContainerIDFile": "",
"LogConfig": {
"Type": "k8s-file",
"Config": null
},
"NetworkMode": "default",
"PortBindings": {},
"RestartPolicy": {
"Name": "",
"MaximumRetryCount": 0
},
"AutoRemove": false,
"VolumeDriver": "",
"VolumesFrom": null,
"CapAdd": [],
"CapDrop": [],
"Dns": [],
"DnsOptions": [],
"DnsSearch": [],
"ExtraHosts": [],
"GroupAdd": [],
"IpcMode": "",
"Cgroup": "",
"Links": null,
"OomScoreAdj": 0,
"PidMode": "",
"Privileged": false,
"PublishAllPorts": false,
"ReadonlyRootfs": false,
"SecurityOpt": [],
"Tmpfs": {},
"UTSMode": "",
"UsernsMode": "",
"ShmSize": 65536000,
"Runtime": "oci",
"ConsoleSize": [
0,
0
],
"Isolation": "",
"CpuShares": 0,
"Memory": 0,
"NanoCpus": 0,
"CgroupParent": "",
"BlkioWeight": 0,
"BlkioWeightDevice": null,
"BlkioDeviceReadBps": null,
"BlkioDeviceWriteBps": null,
"BlkioDeviceReadIOps": null,
"BlkioDeviceWriteIOps": null,
"CpuPeriod": 0,
"CpuQuota": 0,
"CpuRealtimePeriod": 0,
"CpuRealtimeRuntime": 0,
"CpusetCpus": "",
"CpusetMems": "",
"Devices": [],
"DiskQuota": 0,
"KernelMemory": 0,
"MemoryReservation": 0,
"MemorySwap": 0,
"MemorySwappiness": -1,
"OomKillDisable": false,
"PidsLimit": 0,
"Ulimits": [
{
"Name": "RLIMIT_NOFILE",
"Soft": 1048576,
"Hard": 1048576
},
{
"Name": "RLIMIT_NPROC",
"Soft": 1048576,
"Hard": 1048576
}
],
"CpuCount": 0,
"CpuPercent": 0,
"IOMaximumIOps": 0,
"IOMaximumBandwidth": 0
}
}
]
"""
import json
from ansible.module_utils.basic import AnsibleModule
def get_containers_facts(module, executable, name):
if not name:
@ -353,30 +343,32 @@ def get_containers_facts(module, executable, name):
command = [executable, 'container', 'inspect']
command.extend(name)
rc, out, err = module.run_command(command)
if not out or rc != 0:
if rc != 0:
module.fail_json(msg="Unable to gather info for %s: %s" % (name or 'all containers', err))
if not out or json.loads(out) is None:
return [], out, err
return json.loads(out), out, err
def main():
module = AnsibleModule(
argument_spec=yaml.safe_load(DOCUMENTATION)['options'],
argument_spec=dict(
executable=dict(type='str', default='podman'),
name=dict(type='list', elements='str')
),
supports_check_mode=True,
)
executable = module.params['executable']
name = module.params.get('name')
executable = module.get_bin_path(executable, required=True)
name = module.params['name']
executable = module.get_bin_path(module.params['executable'], required=True)
inspect_results, out, err = get_containers_facts(module, executable, name)
results = dict(
changed=False,
containers=inspect_results,
ansible_facts=dict(podman_containers=inspect_results),
stdout=out,
stderr=err
)
results = {
"changed": False,
"containers": inspect_results,
"stderr": err
}
module.exit_json(**results)

View File

@ -15,17 +15,11 @@
# License for the specific language governing permissions and limitations
# under the License.
from __future__ import absolute_import, division, print_function
import json
import yaml
from ansible.module_utils.basic import AnsibleModule
__metaclass__ = type
ANSIBLE_METADATA = {
'metadata_version': '1.0',
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'
}
@ -34,7 +28,7 @@ DOCUMENTATION = """
module: podman_volume_info
author:
- "Sagi Shnaidman (@sshnaidm)"
version_added: '2.9'
version_added: '2.10'
short_description: Gather info about podman volumes
notes: []
description:
@ -65,21 +59,22 @@ RETURN = """
volumes:
description: Facts from all or specified volumes
returned: always
type: dict
sample:
[
{
"name": "testvolume",
"labels": {},
"mountPoint": "/home/ansible/.local/share/testvolume/_data",
"driver": "local",
"options": {},
"scope": "local"
}
]
type: list
sample: [
{
"name": "testvolume",
"labels": {},
"mountPoint": "/home/ansible/.local/share/testvolume/_data",
"driver": "local",
"options": {},
"scope": "local"
}
]
"""
import json
from ansible.module_utils.basic import AnsibleModule
def get_volume_info(module, executable, name):
command = [executable, 'volume', 'inspect']
@ -88,31 +83,32 @@ def get_volume_info(module, executable, name):
else:
command.append("--all")
rc, out, err = module.run_command(command)
if not out or rc != 0:
if rc != 0 or 'no such volume' in err:
module.fail_json(msg="Unable to gather info for %s: %s" % (name or 'all volumes', err))
if not out or json.loads(out) is None:
return [], out, err
return json.loads(out), out, err
def main():
module = AnsibleModule(
argument_spec=yaml.safe_load(DOCUMENTATION)['options'],
argument_spec=dict(
executable=dict(type='str', default='podman'),
name=dict(type='str')
),
supports_check_mode=True,
)
executable = module.params['executable']
name = module.params['name']
executable = module.get_bin_path(executable, required=True)
executable = module.get_bin_path(module.params['executable'], required=True)
inspect_results, out, err = get_volume_info(module, executable, name)
results = dict(
changed=False,
volume=inspect_results,
stdout=out,
stderr=err
)
if name:
results.update({"exists": bool(inspect_results)})
results = {
"changed": False,
"volumes": inspect_results,
"stderr": err
}
module.exit_json(**results)

View File

@ -0,0 +1,33 @@
---
# Based on ansible-lint config
extends: default
rules:
braces:
max-spaces-inside: 1
level: error
brackets:
max-spaces-inside: 1
level: error
colons:
max-spaces-after: -1
level: error
commas:
max-spaces-after: -1
level: error
comments: disable
comments-indentation: disable
document-start: disable
empty-lines:
max: 3
level: error
hyphens:
level: error
indentation: disable
key-duplicates: enable
line-length: disable
new-line-at-end-of-file: disable
new-lines:
type: unix
trailing-spaces: disable
truthy: disable

View File

@ -0,0 +1,56 @@
---
driver:
name: delegated
options:
managed: false
login_cmd_template: >-
ssh
-o UserKnownHostsFile=/dev/null
-o StrictHostKeyChecking=no
-o Compression=no
-o TCPKeepAlive=yes
-o VerifyHostKeyDNS=no
-o ForwardX11=no
-o ForwardAgent=no
{instance}
ansible_connection_options:
ansible_connection: ssh
log: true
platforms:
- name: instance
provisioner:
name: ansible
config_options:
defaults:
fact_caching: jsonfile
fact_caching_connection: /tmp/molecule/facts
inventory:
hosts:
all:
hosts:
instance:
ansible_host: localhost
log: true
env:
ANSIBLE_STDOUT_CALLBACK: yaml
ANSIBLE_ROLES_PATH: "${ANSIBLE_ROLES_PATH:-/usr/share/ansible/roles}:${HOME}/zuul-jobs/roles"
ANSIBLE_LIBRARY: "${ANSIBLE_LIBRARY:-/usr/share/ansible/plugins/modules}"
ANSIBLE_FILTER_PLUGINS: "${ANSIBLE_FILTER_PLUGINS:-/usr/share/ansible/plugins/filter}"
scenario:
name: podman_container
test_sequence:
- prepare
- converge
- verify
lint:
enabled: false
verifier:
name: testinfra
lint:
name: flake8

View File

@ -0,0 +1,393 @@
---
- name: Converge
hosts: all
tasks:
- name: Test podman_container
become: true
block:
- name: Delete all container leftovers from tests
podman_container:
name: "{{ item }}"
state: absent
loop:
- "alpine:3.7"
- "container"
- "container2"
- name: Test no image with default action
podman_container:
name: container
ignore_errors: true
register: no_image
- name: Test no image with state 'started'
podman_container:
name: container
state: started
ignore_errors: true
register: no_image1
- name: Test no image with state 'present'
podman_container:
name: container
state: present
ignore_errors: true
register: no_image2
- name: Check no image
assert:
that:
- no_image is failed
- no_image1 is failed
- no_image2 is failed
- no_image.msg == "State 'started' required image to be configured!"
- no_image1.msg == "State 'started' required image to be configured!"
- no_image2.msg == "State 'present' required image to be configured!"
fail_msg: No image test failed!
success_msg: No image test passed!
- name: Ensure image doesn't exist
podman_image:
name: alpine:3.7
state: absent
- name: Check pulling image
podman_container:
name: container
image: alpine:3.7
state: present
command: sleep 1d
register: image
- name: Check using already pulled image
podman_container:
name: container2
image: alpine:3.7
state: present
command: sleep 1d
register: image2
- name: Check output is correct
assert:
that:
- image is changed
- image.container is defined
- image.container['State']['Running']
- "'pulled image alpine:3.7' in image.actions"
- "'started container' in image.actions"
- image2 is changed
- image2.container is defined
- image2.container['State']['Running']
- "'pulled image alpine:3.7' not in image2.actions"
- "'started container2' in image2.actions"
fail_msg: Pulling image test failed!
success_msg: Pulling image test passed!
- name: Check failed image pull
podman_container:
name: container
image: ineverneverneverexist
state: present
command: sleep 1d
register: imagefail
ignore_errors: true
- name: Check output is correct
assert:
that:
- imagefail is failed
- imagefail.msg == "Can't pull image ineverneverneverexist"
- name: Force container recreate
podman_container:
name: container
image: alpine
state: present
command: sleep 1d
recreate: true
register: recreated
- name: Check output is correct
assert:
that:
- recreated is changed
- recreated.container is defined
- recreated.container['State']['Running']
- "'recreated container' in recreated.actions"
fail_msg: Force recreate test failed!
success_msg: Force recreate test passed!
- name: Stop container
podman_container:
name: container
state: stopped
register: stopped
- name: Stop the same container again (idempotency)
podman_container:
name: container
state: stopped
register: stopped_again
- name: Check output is correct
assert:
that:
- stopped is changed
- stopped.container is defined
- not stopped.container['State']['Running']
- "'stopped container' in stopped.actions"
- stopped_again is not changed
- stopped_again.container is defined
- not stopped_again.container['State']['Running']
- stopped_again.actions == []
fail_msg: Stopping container test failed!
success_msg: Stopping container test passed!
- name: Delete stopped container
podman_container:
name: container
state: absent
register: deleted
- name: Delete again container (idempotency)
podman_container:
name: container
state: absent
register: deleted_again
- name: Check output is correct
assert:
that:
- deleted is changed
- deleted.container is defined
- deleted.container == {}
- "'deleted container' in deleted.actions"
- deleted_again is not changed
- deleted_again.container is defined
- deleted_again.container == {}
- deleted_again.actions == []
fail_msg: Deleting stopped container test failed!
success_msg: Deleting stopped container test passed!
- name: Create container, but don't run
podman_container:
name: container
image: alpine:3.7
state: stopped
command: sleep 1d
register: created
- name: Check output is correct
assert:
that:
- created is changed
- created.container is defined
- created.container != {}
- not created.container['State']['Running']
- "'created container' in created.actions"
fail_msg: "Creating stopped container test failed!"
success_msg: "Creating stopped container test passed!"
- name: Delete created container
podman_container:
name: container
state: absent
- name: Start container that was deleted
podman_container:
name: container
image: alpine:3.7
state: started
command: sleep 1d
register: started
- name: Check output is correct
assert:
that:
- started is changed
- started.container is defined
- started.container['State']['Running']
- "'pulled image alpine:3.7' not in started.actions"
- name: Delete started container
podman_container:
name: container
state: absent
register: deleted
- name: Delete again container (idempotency)
podman_container:
name: container
state: absent
register: deleted_again
- name: Check output is correct
assert:
that:
- deleted is changed
- deleted.container is defined
- deleted.container == {}
- "'deleted container' in deleted.actions"
- deleted_again is not changed
- deleted_again.container is defined
- deleted_again.container == {}
- deleted_again.actions == []
fail_msg: Deleting started container test failed!
success_msg: Deleting started container test passed!
- name: Recreate container with parameters
podman_container:
name: container
image: docker.io/alpine:3.7
state: started
command: sleep 1d
recreate: true
etc_hosts:
host1: 127.0.0.1
host2: 127.0.0.1
annotation:
this: "annotation_value"
dns:
- 1.1.1.1
- 8.8.4.4
dns_search: example.com
cap_add:
- SYS_TIME
- NET_ADMIN
publish:
- "9000:80"
- "9001:8000"
workdir: "/bin"
env:
FOO: bar
BAR: foo
TEST: 1
BOOL: false
group_add: "somegroup"
label:
somelabel: labelvalue
otheralbe: othervalue
volumes:
- /tmp:/data
register: test
- name: Check output is correct
assert:
that:
- test is changed
- test.container is defined
- test.container != {}
- test.container['State']['Running']
# test capabilities
- "'CAP_SYS_TIME' in test.container['BoundingCaps']"
- "'CAP_NET_ADMIN' in test.container['BoundingCaps']"
# test annotations
- test.container['Config']['Annotations']['this'] is defined
- test.container['Config']['Annotations']['this'] == "annotation_value"
# test DNS
- >-
(test.container['HostConfig']['Dns'] is defined and
test.container['HostConfig']['Dns'] == ['1.1.1.1', '8.8.4.4']) or
(test.container['HostConfig']['DNS'] is defined and
test.container['HostConfig']['DNS'] == ['1.1.1.1', '8.8.4.4'])
# test ports
- test.container['NetworkSettings']['Ports']|length == 2
# test working dir
- test.container['Config']['WorkingDir'] == "/bin"
# test dns search
- >-
(test.container['HostConfig']['DnsSearch'] is defined and
test.container['HostConfig']['DnsSearch'] == ['example.com']) or
(test.container['HostConfig']['DNSSearch'] is defined and
test.container['HostConfig']['DNSSearch'] == ['example.com'])
# test environment variables
- "'FOO=bar' in test.container['Config']['Env']"
- "'BAR=foo' in test.container['Config']['Env']"
- "'TEST=1' in test.container['Config']['Env']"
- "'BOOL=False' in test.container['Config']['Env']"
# test labels
- test.container['Config']['Labels'] | length == 2
- test.container['Config']['Labels']['somelabel'] == "labelvalue"
- test.container['Config']['Labels']['otheralbe'] == "othervalue"
# test mounts
- >-
(test.container['Mounts'][0]['Destination'] is defined and
'/data' in test.container['Mounts'] | map(attribute='Destination') | list) or
(test.container['Mounts'][0]['destination'] is defined and
'/data' in test.container['Mounts'] | map(attribute='destination') | list)
- >-
(test.container['Mounts'][0]['Source'] is defined and
'/tmp' in test.container['Mounts'] | map(attribute='Source') | list) or
(test.container['Mounts'][0]['source'] is defined and
'/tmp' in test.container['Mounts'] | map(attribute='source') | list)
fail_msg: Parameters container test failed!
success_msg: Parameters container test passed!
- name: Check basic idempotency of running container
podman_container:
name: testidem
image: alpine
state: present
command: sleep 20m
- name: Check basic idempotency of running container - run it again
podman_container:
name: testidem
image: alpine
state: present
command: sleep 20m
register: idem
- name: Check that nothing was changed
assert:
that:
- not idem.changed
- name: Run changed container (with tty enabled)
podman_container:
name: testidem
image: alpine
state: present
command: sleep 20m
tty: true
register: idem1
- name: Check that container is recreated when changed
assert:
that:
- idem1 is changed
- name: Run changed container without specifying an option, use defaults
podman_container:
name: testidem
image: alpine
state: present
command: sleep 20m
register: idem2
- name: Check that container is recreated when changed to default value
assert:
that:
- idem2 is changed
- name: Remove container
podman_container:
name: testidem
state: absent
register: remove
- name: Check podman_actions
assert:
that:
- "'podman rm -f testidem' in remove.podman_actions"
always:
- name: Delete all container leftovers from tests
podman_container:
name: "{{ item }}"
state: absent
loop:
- "alpine:3.7"
- "container"
- "container2"

View File

@ -0,0 +1,39 @@
---
# Copyright 2019 Red Hat, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
- name: Prepare
hosts: all
gather_facts: true
roles:
- role: test_deps
test_deps_extra_packages:
- podman
post_tasks:
- name: Check podman version
command: podman version
register: p_ver
changed_when: false
- name: Print podman version
debug:
msg: |
podman version:
{{ p_ver.stdout }}
Testing with ansible {{ ansible_version.full }}
with python {{ ansible_python_version }}
on host {{ ansible_distribution }} {{ ansible_distribution_version }}

View File

@ -0,0 +1,56 @@
---
driver:
name: delegated
options:
managed: false
login_cmd_template: >-
ssh
-o UserKnownHostsFile=/dev/null
-o StrictHostKeyChecking=no
-o Compression=no
-o TCPKeepAlive=yes
-o VerifyHostKeyDNS=no
-o ForwardX11=no
-o ForwardAgent=no
{instance}
ansible_connection_options:
ansible_connection: ssh
log: true
platforms:
- name: instance
provisioner:
name: ansible
config_options:
defaults:
fact_caching: jsonfile
fact_caching_connection: /tmp/molecule/facts
inventory:
hosts:
all:
hosts:
instance:
ansible_host: localhost
log: true
env:
ANSIBLE_STDOUT_CALLBACK: yaml
ANSIBLE_ROLES_PATH: "${ANSIBLE_ROLES_PATH:-/usr/share/ansible/roles}:${HOME}/zuul-jobs/roles"
ANSIBLE_LIBRARY: "${ANSIBLE_LIBRARY:-/usr/share/ansible/plugins/modules}"
ANSIBLE_FILTER_PLUGINS: "${ANSIBLE_FILTER_PLUGINS:-/usr/share/ansible/plugins/filter}"
scenario:
name: podman_container_info
test_sequence:
- prepare
- converge
- verify
lint:
enabled: false
verifier:
name: testinfra
lint:
name: flake8

View File

@ -0,0 +1,70 @@
---
- name: Converge
hosts: all
tasks:
- name: Test podman_container_info
become: true
block:
- name: Generate random value for container name
set_fact:
container_name: "{{ 'ansible-test-podman-%0x' % ((2**32) | random) }}"
- name: Make sure container doesn't exist
command: podman container rm -f {{ container_name }}
ignore_errors: true
- name: Get missing container info
podman_container_info:
name: "{{ container_name }}"
register: nonexist
ignore_errors: true
- name: Check results
assert:
that:
- "'containers' not in nonexist"
- nonexist is failed
- name: Make sure container exists
command: podman container run -d --name {{ container_name }} alpine sleep 15m
- name: Get existing container info
podman_container_info:
name: "{{ container_name }}"
register: existing_container
- name: Get all containers info
podman_container_info:
register: all_containers
- name: Dump podman container inspect result
debug: var=existing_container
- name: Comparison with 'podman container inspect'
command: podman container inspect "{{ container_name }}"
register: podman_inspect
- name: Convert podman inspect output to JSON
set_fact:
podman_inspect_result: "{{ podman_inspect.stdout | from_json }}"
- name: Cleanup
command: podman container rm -f {{ container_name }}
- name: Make checks
assert:
that:
- "'containers' in existing_container"
- existing_container.containers
- "existing_container.containers == podman_inspect_result"
- all_containers.containers == existing_container.containers
always:
- name: Delete all container leftovers from tests
podman_container:
name: "{{ item }}"
state: absent
loop:
- "alpine:3.7"
- "container"
- "container2"

View File

@ -0,0 +1,22 @@
---
# Copyright 2019 Red Hat, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
- name: Prepare
hosts: all
roles:
- role: test_deps
test_deps_extra_packages:
- podman

View File

@ -0,0 +1,56 @@
---
driver:
name: delegated
options:
managed: false
login_cmd_template: >-
ssh
-o UserKnownHostsFile=/dev/null
-o StrictHostKeyChecking=no
-o Compression=no
-o TCPKeepAlive=yes
-o VerifyHostKeyDNS=no
-o ForwardX11=no
-o ForwardAgent=no
{instance}
ansible_connection_options:
ansible_connection: ssh
log: true
platforms:
- name: instance
provisioner:
name: ansible
config_options:
defaults:
fact_caching: jsonfile
fact_caching_connection: /tmp/molecule/facts
inventory:
hosts:
all:
hosts:
instance:
ansible_host: localhost
log: true
env:
ANSIBLE_STDOUT_CALLBACK: yaml
ANSIBLE_ROLES_PATH: "${ANSIBLE_ROLES_PATH:-/usr/share/ansible/roles}:${HOME}/zuul-jobs/roles"
ANSIBLE_LIBRARY: "${ANSIBLE_LIBRARY:-/usr/share/ansible/plugins/modules}"
ANSIBLE_FILTER_PLUGINS: "${ANSIBLE_FILTER_PLUGINS:-/usr/share/ansible/plugins/filter}"
scenario:
name: podman_volume_info
test_sequence:
- prepare
- converge
- verify
lint:
enabled: false
verifier:
name: testinfra
lint:
name: flake8

View File

@ -0,0 +1,63 @@
---
- name: Converge
hosts: all
tasks:
- name: Test podman_volume_info
become: true
block:
- name: Print podman version
command: podman version
- name: Generate random value for volume name
set_fact:
volume_name: "{{ 'ansible-test-podman-%0x' % ((2**32) | random) }}"
- name: Make sure volume doesn't exist
command: podman volume rm {{ volume_name }}
ignore_errors: true
- name: Get missing volume info
podman_volume_info:
name: "{{ volume_name }}"
register: nonexist
ignore_errors: true
- name: Check results
assert:
that:
- "'volumes' not in nonexist"
- nonexist is failed
- name: Make sure volume exists
command: podman volume create {{ volume_name }}
- name: Get existing volume info
podman_volume_info:
name: "{{ volume_name }}"
register: existing_volume
- name: Dump podman volume inspect result
debug: var=existing_volume
- name: Comparison with 'podman volume inspect'
command: podman volume inspect "{{ volume_name }}"
register: podman_inspect
- name: Convert podman inspect output to JSON
set_fact:
podman_inspect_result: "{{ podman_inspect.stdout | from_json }}"
- name: Cleanup
command: podman volume rm {{ volume_name }}
- name: Make checks
assert:
that:
- "'volumes' in existing_volume"
- existing_volume.volumes
- "existing_volume.volumes == podman_inspect_result"
always:
- name: Cleanup
command: podman volume rm {{ volume_name }}
ignore_errors: true

View File

@ -0,0 +1,22 @@
---
# Copyright 2019 Red Hat, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
- name: Prepare
hosts: all
roles:
- role: test_deps
test_deps_extra_packages:
- podman

View File

@ -1,351 +0,0 @@
---
- name: Test podman_image
when:
- ansible_facts.virtualization_type != 'docker'
- ansible_facts.distribution in ['RedHat', 'Fedora', 'CentOS']
block:
- name: Delete all container leftovers from tests
podman_container:
name: "{{ item }}"
state: absent
loop:
- "alpine:3.7"
- "container"
- "container2"
- name: Test no image with default action
podman_container:
name: container
ignore_errors: true
register: no_image
- name: Test no image with state 'started'
podman_container:
name: container
state: started
ignore_errors: true
register: no_image1
- name: Test no image with state 'present'
podman_container:
name: container
state: present
ignore_errors: true
register: no_image2
- name: Check no image
assert:
that:
- no_image is failed
- no_image1 is failed
- no_image2 is failed
- no_image.msg == "State 'started' required image to be configured!"
- no_image1.msg == "State 'started' required image to be configured!"
- no_image2.msg == "State 'present' required image to be configured!"
fail_msg: No image test failed!
success_msg: No image test passed!
- name: Ensure image doesn't exist
podman_image:
name: alpine:3.7
state: absent
- name: Check pulling image
podman_container:
name: container
image: alpine:3.7
state: present
command: sleep 1d
register: image
- name: Check using already pulled image
podman_container:
name: container2
image: alpine:3.7
state: present
command: sleep 1d
register: image2
- name: Check output is correct
assert:
that:
- image is changed
- image.ansible_facts is defined
- image.ansible_facts.podman_container is defined
- image.ansible_facts.podman_container['State']['Running']
- image.container is defined
- image.container['State']['Running']
- "'pulled image alpine:3.7' in image.actions"
- "'started container' in image.actions"
- image2 is changed
- image2.ansible_facts is defined
- image2.ansible_facts.podman_container is defined
- image2.ansible_facts.podman_container['State']['Running']
- image2.container is defined
- image2.container['State']['Running']
- "'pulled image alpine:3.7' not in image2.actions"
- "'started container2' in image2.actions"
fail_msg: Pulling image test failed!
success_msg: Pulling image test passed!
- name: Check failed image pull
podman_container:
name: container
image: ineverneverneverexist
state: present
command: sleep 1d
register: imagefail
ignore_errors: true
- name: Check output is correct
assert:
that:
- imagefail is failed
- imagefail.msg == "Can't pull image ineverneverneverexist"
- name: Force container recreate
podman_container:
name: container
image: alpine
state: present
command: sleep 1d
recreate: true
register: recreated
- name: Check output is correct
assert:
that:
- recreated is changed
- recreated.ansible_facts is defined
- recreated.ansible_facts.podman_container is defined
- recreated.ansible_facts.podman_container['State']['Running']
- "'recreated container' in recreated.actions"
fail_msg: Force recreate test failed!
success_msg: Force recreate test passed!
- name: Stop container
podman_container:
name: container
state: stopped
register: stopped
- name: Stop the same container again (idempotency)
podman_container:
name: container
state: stopped
register: stopped_again
- name: Check output is correct
assert:
that:
- stopped is changed
- stopped.ansible_facts is defined
- stopped.ansible_facts.podman_container is defined
- not stopped.ansible_facts.podman_container['State']['Running']
- "'stopped container' in stopped.actions"
- stopped_again is not changed
- stopped_again.ansible_facts is defined
- stopped_again.ansible_facts.podman_container is defined
- not stopped_again.ansible_facts.podman_container['State']['Running']
- stopped_again.actions == []
fail_msg: Stopping container test failed!
success_msg: Stopping container test passed!
- name: Delete stopped container
podman_container:
name: container
state: absent
register: deleted
- name: Delete again container (idempotency)
podman_container:
name: container
state: absent
register: deleted_again
- name: Check output is correct
assert:
that:
- deleted is changed
- deleted.ansible_facts is defined
- deleted.ansible_facts.podman_container is defined
- deleted.ansible_facts.podman_container == {}
- "'deleted container' in deleted.actions"
- deleted_again is not changed
- deleted_again.ansible_facts is defined
- deleted_again.ansible_facts.podman_container is defined
- deleted_again.ansible_facts.podman_container == {}
- deleted_again.actions == []
fail_msg: Deleting stopped container test failed!
success_msg: Deleting stopped container test passed!
- name: Create container, but don't run
podman_container:
name: container
image: alpine:3.7
state: stopped
command: sleep 1d
register: created
- name: Check output is correct
assert:
that:
- created is changed
- created.ansible_facts is defined
- created.ansible_facts.podman_container is defined
- created.ansible_facts.podman_container != {}
- not created.ansible_facts.podman_container['State']['Running']
- "'created container' in created.actions"
fail_msg: "Creating stopped container test failed!"
success_msg: "Creating stopped container test passed!"
- name: Delete created container
podman_container:
name: container
state: absent
- name: Start container that was deleted
podman_container:
name: container
image: alpine:3.7
state: started
command: sleep 1d
register: started
- name: Check output is correct
assert:
that:
- started is changed
- started.ansible_facts is defined
- started.ansible_facts.podman_container is defined
- started.ansible_facts.podman_container['State']['Running']
- started.container is defined
- started.container['State']['Running']
- "'pulled image alpine:3.7' not in started.actions"
- name: Delete started container
podman_container:
name: container
state: absent
register: deleted
- name: Delete again container (idempotency)
podman_container:
name: container
state: absent
register: deleted_again
- name: Check output is correct
assert:
that:
- deleted is changed
- deleted.ansible_facts is defined
- deleted.ansible_facts.podman_container is defined
- deleted.ansible_facts.podman_container == {}
- "'deleted container' in deleted.actions"
- deleted_again is not changed
- deleted_again.ansible_facts is defined
- deleted_again.ansible_facts.podman_container is defined
- deleted_again.ansible_facts.podman_container == {}
- deleted_again.actions == []
fail_msg: Deleting started container test failed!
success_msg: Deleting started container test passed!
- name: Recreate container with parameters
podman_container:
name: container
image: alpine:3.7
state: started
command: sleep 1d
recreate: true
etc_hosts:
host1: 127.0.0.1
host2: 127.0.0.1
annotation:
this: "annotation_value"
dns:
- 1.1.1.1
- 8.8.4.4
dns_search: example.com
cap_add:
- SYS_TIME
- NET_ADMIN
publish:
- "9000:80"
- "9001:8000"
workdir: "/bin"
env:
FOO: bar
BAR: foo
TEST: 1
BOOL: false
group_add: "somegroup"
label:
somelabel: labelvalue
otheralbe: othervalue
volumes:
- /tmp:/data
register: test
- name: Check output is correct
assert:
that:
- test is changed
- test.ansible_facts is defined
- test.ansible_facts.podman_container is defined
- test.ansible_facts.podman_container != {}
- test.ansible_facts.podman_container['State']['Running']
# test capabilities
- "'CAP_SYS_TIME' in test.ansible_facts.podman_container['BoundingCaps']"
- "'CAP_NET_ADMIN' in test.ansible_facts.podman_container['BoundingCaps']"
# test annotations
- test.ansible_facts.podman_container['Config']['Annotations']['this'] is defined
- test.ansible_facts.podman_container['Config']['Annotations']['this'] == "annotation_value"
# test DNS
- >-
(test.ansible_facts.podman_container['HostConfig']['Dns'] is defined and
test.ansible_facts.podman_container['HostConfig']['Dns'] == ['1.1.1.1', '8.8.4.4']) or
(test.ansible_facts.podman_container['HostConfig']['DNS'] is defined and
test.ansible_facts.podman_container['HostConfig']['DNS'] == ['1.1.1.1', '8.8.4.4'])
# test ports
- test.ansible_facts.podman_container['NetworkSettings']['Ports']|length == 2
# test working dir
- test.ansible_facts.podman_container['Config']['WorkingDir'] == "/bin"
# test dns search
- >-
(test.ansible_facts.podman_container['HostConfig']['DnsSearch'] is defined and
test.ansible_facts.podman_container['HostConfig']['DnsSearch'] == ['example.com']) or
(test.ansible_facts.podman_container['HostConfig']['DNSSearch'] is defined and
test.ansible_facts.podman_container['HostConfig']['DNSSearch'] == ['example.com'])
# test environment variables
- "'FOO=bar' in test.ansible_facts.podman_container['Config']['Env']"
- "'BAR=foo' in test.ansible_facts.podman_container['Config']['Env']"
- "'TEST=1' in test.ansible_facts.podman_container['Config']['Env']"
- "'BOOL=False' in test.ansible_facts.podman_container['Config']['Env']"
# test labels
- test.ansible_facts.podman_container['Config']['Labels'] | length == 2
- test.ansible_facts.podman_container['Config']['Labels']['somelabel'] == "labelvalue"
- test.ansible_facts.podman_container['Config']['Labels']['otheralbe'] == "othervalue"
# test mounts
- >-
(test.ansible_facts.podman_container['Mounts'][0]['Destination'] is defined and
'/data' in test.ansible_facts.podman_container['Mounts'] | map(attribute='Destination') | list) or
(test.ansible_facts.podman_container['Mounts'][0]['destination'] is defined and
'/data' in test.ansible_facts.podman_container['Mounts'] | map(attribute='destination') | list)
- >-
(test.ansible_facts.podman_container['Mounts'][0]['Source'] is defined and
'/tmp' in test.ansible_facts.podman_container['Mounts'] | map(attribute='Source') | list) or
(test.ansible_facts.podman_container['Mounts'][0]['source'] is defined and
'/tmp' in test.ansible_facts.podman_container['Mounts'] | map(attribute='source') | list)
fail_msg: Parameters container test failed!
success_msg: Parameters container test passed!
always:
- name: Delete all container leftovers from tests
podman_container:
name: "{{ item }}"
state: absent
loop:
- "alpine:3.7"
- "container"
- "container2"

View File

@ -0,0 +1,29 @@
---
# Copyright 2019 Red Hat, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
# All variables intended for modification should place placed in this file.
# All variables within this role should have a prefix of "tripleo_container_manage"
tripleo_container_manage_cli: podman
tripleo_container_manage_concurrency: 1
tripleo_container_manage_config: "/var/lib/tripleo-config/"
tripleo_container_manage_config_id: tripleo
tripleo_container_manage_config_patterns: 'hashed-*.json'
tripleo_container_manage_debug: false
tripleo_container_manage_healthcheck_disabled: false
tripleo_container_manage_log_path: '/var/log/containers/stdouts'
tripleo_container_manage_systemd_order: false

View File

@ -0,0 +1 @@
enable netns-placeholder.service

View File

@ -0,0 +1 @@
enable tripleo-container-shutdown.service

View File

@ -0,0 +1,11 @@
[Unit]
Description=Create netns directory
Before=tripleo-container-shutdown.service
Wants=network.target
[Service]
Type=oneshot
ExecStart=/sbin/ip netns add placeholder
ExecStop=/sbin/ip netns delete placeholder
KillMode=process
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,19 @@
#!/usr/bin/env bash
TIMEOUT=${1:-90}
PARALLEL=${2:-10}
if command -v dnf >/dev/null;then
if command -v podman >/dev/null; then
containers=$(podman ps --filter label=managed_by=tripleo_ansible --format {{.Names}})
for c in $containers; do
logger -p warning "WARNING ($c) Container $c managed by tripleo-ansible is not stopped yet"
logger -p warning "WARNING ($c) Check systemd logs: journalctl -u tripleo_$c"
done
fi
else
if command -v docker >/dev/null; then
/usr/bin/docker ps --format \"{{.Names}}\" --filter "label=managed_by=tripleo_ansible" | \
/usr/bin/xargs -n 1 -P $PARALLEL /usr/bin/docker stop --time=$TIMEOUT
fi
fi

View File

@ -0,0 +1,21 @@
[Unit]
Description=TripleO Container Shutdown
Documentation=https://docs.openstack.org/tripleo-docs/
# Note: docker.service will be removed once CentOS8 / RHEL8 will be the default
# platform, but for now we keep it for Pacemaker testing.
# pacemaker.service is needed here, to make sure that all non-Pacemaker managed
# containers are stopped before Pacemaker.
After=pacemaker.service docker.service network-online.target iptables.service ip6tables.service
Before=shutdown.target
RefuseManualStop=yes
[Service]
Type=oneshot
ExecStart=/bin/true
RemainAfterExit=yes
ExecStop=/usr/libexec/tripleo-container-shutdown
# Wait at most 900 seconds for all containers to shutdown
TimeoutStopSec=900
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,83 @@
#!/usr/bin/env bash
PODMAN=/usr/bin/podman
NAME=$1
if [ -z "$NAME" ]; then
echo "No name provided, cannot start container. Aborting" >&2
exit 1
fi
# Start container. Podman does not fail if container is already started
$PODMAN start $NAME
rc=$?
if [ $rc -ne 0 ]; then
echo "Error starting podman container $NAME: $rc" >&2
exit $rc
fi
# The environment can ben configured to create additional drop-in
# dependencies for the scopes associated with the container. This is
# done to prevent systemd from stopping the scopes early and break the
# configured dependencies in tripleo_*.services
# Stop here otherwise.
if [ ! -f "/etc/sysconfig/podman_drop_in" ]; then
exit 0
fi
# Retrieve the container's ID
# Note: currently the only API to retrieve the CID is either
# 1) via "podman inspect" but we don't want to use it because it can be
# very slow under IO load.
# 2) by running "podman start $NAME" but that command only returns the CID
# if the container is already running. Otherwise it returns the container
# name, which would break us.
# The only other means is via "podman ps". ps option "--filter" cannot
# enforce full name matches, so use grep instead and stop at first match.
CID=$($PODMAN ps --no-trunc --format '{{.ID}} {{.Names}}' | grep -F -w -m1 "$NAME" | cut -d' ' -f1)
if [ -z "$CID" ]; then
echo "Container ID not found for \"$NAME\". Not creating drop-in dependency" 2>&1
exit 1
else
echo "Creating additional drop-in dependency for \"$NAME\" ($CID)"
fi
# Note: a tripleo-ansible container has three systemd files associated with it:
# 1. tripleo_*.service - the regular systemd service generated by tripleo-ansible
# 2. libpod-conmon*.scope - created dynamically by podman. runs a conmon
# process that creates a pidfile for tripleo_*.service and monitor it.
# 3. libpod-*.scope - created dynamically by runc. for cgroups accounting
#
# tripleo-ansible can only set start/stop dependencies on 1., not 2. and 3.
# On reboot, systemd is allowed to stop 2. or 3. at any time, which can
# cause 1. to stop before its deps as set up by tripleo-ansible.
#
# To prevent an unexpected stop of 1. from happening, inject a dependency
# in 2. and 3. so that systemd is forbidden to stop those scopes
# automatically until tripleo-container-shutdown.service is stopped.
# That way, when systemd stops 1., the two scopes 2. and 3. will
# finish in sequence and tripleo-ansible dependencies will be respected.
for scope in "libpod-$CID.scope.d" "libpod-conmon-$CID.scope.d"; do
if [ $rc -eq 0 ] && [ ! -d /run/systemd/transient/"$scope" ]; then
mkdir -p /run/systemd/transient/"$scope" && \
echo -e "[Unit]\nBefore=tripleo-container-shutdown.service" > /run/systemd/transient/"$scope"/dep.conf && \
chmod ago+r /run/systemd/transient/"$scope" /run/systemd/transient/"$scope"/dep.conf
rc=$?
fi
done
if [ $rc -ne 0 ]; then
echo "Could not create drop-in dependency for \"$NAME\" ($CID)" >&2
exit 1
fi
systemctl daemon-reload
rc=$?
if [ $rc -ne 0 ]; then
echo "Could not refresh service definition after creating drop-in for \"$NAME\": $rc" >&2
exit 1
fi

View File

@ -0,0 +1,44 @@
---
# Copyright 2019 Red Hat, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
galaxy_info:
author: OpenStack
description: TripleO OpenStack Role -- tripleo-container-manage
company: Red Hat
license: Apache-2.0
min_ansible_version: 2.7
#
# Provide a list of supported platforms, and for each platform a list of versions.
# If you don't wish to enumerate all versions for a particular platform, use 'all'.
# To view available platforms and versions (or releases), visit:
# https://galaxy.ansible.com/api/v1/platforms/
#
platforms:
- name: Fedora
versions:
- 28
- name: CentOS
versions:
- 7
galaxy_tags:
- tripleo
# List your role dependencies here, one per line. Be sure to remove the '[]' above,
# if you add dependencies to this list.
dependencies: []

View File

@ -0,0 +1,56 @@
---
driver:
name: delegated
options:
managed: false
login_cmd_template: >-
ssh
-o UserKnownHostsFile=/dev/null
-o StrictHostKeyChecking=no
-o Compression=no
-o TCPKeepAlive=yes
-o VerifyHostKeyDNS=no
-o ForwardX11=no
-o ForwardAgent=no
{instance}
ansible_connection_options:
ansible_connection: ssh
log: true
platforms:
- name: instance
provisioner:
name: ansible
config_options:
defaults:
fact_caching: jsonfile
fact_caching_connection: /tmp/molecule/facts
inventory:
hosts:
all:
hosts:
instance:
ansible_host: localhost
log: true
env:
ANSIBLE_STDOUT_CALLBACK: yaml
ANSIBLE_ROLES_PATH: "${ANSIBLE_ROLES_PATH:-/usr/share/ansible/roles}:${HOME}/zuul-jobs/roles"
ANSIBLE_LIBRARY: "${ANSIBLE_LIBRARY:-/usr/share/ansible/plugins/modules}"
ANSIBLE_FILTER_PLUGINS: "${ANSIBLE_FILTER_PLUGINS:-/usr/share/ansible/plugins/filter}"
scenario:
name: default
test_sequence:
- prepare
- converge
- verify
lint:
enabled: false
verifier:
name: testinfra
lint:
name: flake8

View File

@ -0,0 +1,62 @@
---
# Copyright 2019 Red Hat, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
- name: Converge
hosts: all
gather_facts: false
vars:
tripleo_container_manage_config: '/tmp/container-configs'
tripleo_container_manage_debug: true
tripleo_container_manage_config_patterns: '*.json'
tripleo_container_manage_systemd_order: true
tasks:
- include_role:
name: tripleo-container-manage
post_tasks:
- name: Verify that Fedora container was created correctly
become: true
block:
- name: Check for fedora container
command: podman container exists fedora
- name: Gather facts about fedora container
podman_container_info:
name: fedora
register: fedora_infos
- name: Assert that fedora container has the right image
assert:
that:
- "'fedora:latest' in fedora_infos.containers.0.ImageName"
fail_msg: 'fedora container has wrong image'
success_msg: 'fedora container has the right image'
- name: Check if tripleo_fedora systemd service is active
command: systemctl is-active --quiet tripleo_fedora
register: tripleo_fedora_active_result
- name: Assert that tripleo_fedora systemd service is active
assert:
that:
- tripleo_fedora_active_result.rc == 0
fail_msg: 'tripleo_fedora systemd service is not active'
success_msg: 'tripleo_fedora systemd service is active'
- name: Check if tripleo_fedora systemd healthcheck service is active
command: systemctl is-active --quiet tripleo_fedora_healthcheck.timer
register: tripleo_fedora_healthcheck_active_result
- name: Assert that tripleo_fedora systemd healthcheck service is active
assert:
that:
- tripleo_fedora_healthcheck_active_result.rc == 0
fail_msg: 'tripleo_fedora systemd healthcheck service is not active'
success_msg: 'tripleo_fedora systemd healthcheck service is active'

View File

@ -0,0 +1,40 @@
---
# Copyright 2019 Red Hat, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
- name: Prepare
hosts: all
roles:
- role: test_deps
test_deps_extra_packages:
- podman
tasks:
- name: Prepare the container configs directory
file:
path: '/tmp/container-configs'
state: directory
- name: Create a configuration file for a fedora container
copy:
content: |
{
"image": "fedora:latest",
"net": "host",
"command": "sleep 3600",
"restart": "always",
"net": "host",
"healthcheck": { "test": "echo test" }
}
dest: '/tmp/container-configs/fedora.json'

View File

@ -0,0 +1,29 @@
---
# Copyright 2019 Red Hat, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
- name: "Get {{ lookup('dict', container_exists_data).value.command.0 }} container status"
set_fact:
container_running: >-
{{ podman_containers.containers | selectattr('Name', 'equalto', lookup('dict', container_exists_data).value.command.0) |
map(attribute='State.Running') | first | default(false) }}
- name: "Fail if {{ lookup('dict', container_exists_data).value.command.0 }} is not running"
fail:
msg: >-
Can't run container exec for {{ lookup('dict', container_exists_data).key }},
{{ lookup('dict', container_exists_data).value.command.0 }} is not running
when:
- not container_running|default(false)|bool

View File

@ -0,0 +1,21 @@
---
# Copyright 2019 Red Hat, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
- name: "Create containers managed by Podman for {{ tripleo_container_manage_config }}"
when:
- tripleo_container_manage_cli == 'podman'
include: podman/start_order.yml order="{{ item.key }}" data="{{ item.value }}"
loop: "{{ all_containers_hash | subsort(attribute='start_order', null_value=0) | dict2items | list }}"

View File

@ -0,0 +1,27 @@
---
# Copyright 2019 Red Hat, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
- name: Gather podman infos
podman_container_info: {}
register: podman_containers
when:
- tripleo_container_manage_cli == 'podman'
- name: "Delete containers managed by Podman for {{ tripleo_container_manage_config }}"
when:
- tripleo_container_manage_cli == 'podman'
include_tasks: podman/delete.yml
loop: "{{ podman_containers.containers | needs_delete(config=all_containers_hash, config_id=tripleo_container_manage_config_id) }}"

View File

@ -0,0 +1,95 @@
---
# Copyright 2019 Red Hat, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
# "tripleo-container-manage" will search for and load any operating system variable file
# found within the "vars/" path. If no OS files are found the task will skip.
- name: Gather variables for each operating system
include_vars: "{{ item }}"
with_first_found:
- skip: true
files:
- "{{ ansible_distribution | lower }}-{{ ansible_distribution_version | lower }}.yml"
- "{{ ansible_distribution | lower }}-{{ ansible_distribution_major_version | lower }}.yml"
- "{{ ansible_os_family | lower }}-{{ ansible_distribution_major_version | lower }}.yml"
- "{{ ansible_distribution | lower }}.yml"
- "{{ ansible_os_family | lower }}-{{ ansible_distribution_version.split('.')[0] }}.yml"
- "{{ ansible_os_family | lower }}.yml"
tags:
- always
- name: Create container logs path
file:
path: "{{ tripleo_container_manage_log_path }}"
state: directory
owner: root
group: root
become: true
- name: Create ansible-managed dropin file
copy:
dest: "{{ tripleo_container_manage_config | dirname }}/.ansible-managed"
content: |
Containers are managed by the tripleo-container-manage role in
tripleo-ansible project. This file is used by paunch to show a warning
if paunch is used against a deployment done with tripleo-container-manage.
- name: Generate containers configs data
no_log: "{{ false if tripleo_container_manage_debug else true }}"
block:
- name: "Find all matching configs configs for in {{ tripleo_container_manage_config }}"
find:
paths: "{{ tripleo_container_manage_config }}"
patterns: "{{ tripleo_container_manage_config_patterns }}"
recurse: true
register: matched_files
- name: "Read config for each container in {{ tripleo_container_manage_config }}"
slurp:
src: "{{ item.path }}"
register: containers_data
loop: "{{ matched_files.files }}"
- name: Prepare container hashes from config
set_fact:
container_hash: "{'{{ item.source|basename|regex_replace('^hashed-','')|regex_replace('.json$','') }}': {{ item.content|b64decode|from_json }} }"
register: container_hashes
loop: "{{ containers_data['results'] }}"
- name: Compile container hashes from results
set_fact:
container_hash: "{{ item.ansible_facts.container_hash | combine(item.ansible_facts.container_hash) }}"
register: container_hashes
loop: "{{ container_hashes.results }}"
- name: Finalise hashes for all containers
set_fact:
all_containers_hash: "{{ container_hashes.results | map(attribute='ansible_facts.container_hash') | list | singledict() }}"
- name: "Manage systemd shutdown files"
become: true
when:
- tripleo_container_manage_systemd_order
block:
- name: Include tasks for systemd shutdown service
include_tasks: shutdown.yml
- name: "Manage containers from {{ tripleo_container_manage_config }}"
when:
- tripleo_container_manage_cli == 'podman'
become: true
block:
- name: "Delete containers from {{ tripleo_container_manage_config }}"
include_tasks: delete.yml
- name: "Create containers from {{ tripleo_container_manage_config }}"
include_tasks: create.yml

View File

@ -0,0 +1,93 @@
---
# Copyright 2019 Red Hat, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
- name: "Async container create/run"
async: 300
poll: 0
register: create_async_results
# async and check mode don't work together
when:
- not ansible_check_mode|bool
loop: "{{ batched_container_data | haskey(attribute='action', reverse=True) }}"
loop_control:
loop_var: container_data
podman_container:
cap_add: "{{ lookup('dict', container_data).value.cap_add | default(omit) }}"
cap_drop: "{{ lookup('dict', container_data).value.cap_drop | default(omit) }}"
command: "{{ lookup('dict', container_data).value.command | default(omit) }}"
conmon_pidfile: "/var/run/{{ lookup('dict', container_data).key }}.pid"
cpu_shares: "{{ lookup('dict', container_data).value.cpu_shares | default(omit) | int }}"
# cpuset_cpus: "{{ lookup('dict', container_data).value.cpuset_cpus | default(omit) }}"
debug: true
detach: "{{ lookup('dict', container_data).value.detach | default(true) }}"
env: "{{ lookup('dict', container_data).value.environment | default(omit) }}"
env_file: "{{ lookup('dict', container_data).value.env_file | default(omit) }}"
etc_hosts: "{{ lookup('dict', container_data).value.extra_hosts | default({}) }}"
group_add: "{{ lookup('dict', container_data).value.group_add | default(omit) }}"
hostname: "{{ lookup('dict', container_data).value.hostname | default(omit) }}"
image: "{{ lookup('dict', container_data).value.image }}"
interactive: "{{ lookup('dict', container_data).value.interactive | default(false) }}"
ipc: "{{ lookup('dict', container_data).value.ipc | default(omit) }}"
label:
config_id: "{{ tripleo_container_manage_config_id }}"
container_name: "{{ lookup('dict', container_data).key }}"
managed_by: tripleo_ansible
config_data: "{{ lookup('dict', container_data).value }}"
log_driver: 'k8s-file'
log_opt: "path={{ tripleo_container_manage_log_path }}/{{ lookup('dict', container_data).key }}.log"
memory: "{{ lookup('dict', container_data).value.mem_limit | default(omit) }}"
memory_swap: "{{ lookup('dict', container_data).value.mem_swappiness | default(omit) }}"
name: "{{ lookup('dict', container_data).key }}"
net: "{{ lookup('dict', container_data).value.net | default('none') }}"
pid: "{{ lookup('dict', container_data).value.pid | default(omit) }}"
privileged: "{{ lookup('dict', container_data).value.privileged | default(false) }}"
rm: "{{ lookup('dict', container_data).value.remove | default(false) }}"
security_opt: "{{ lookup('dict', container_data).value.security_opt | default(omit) }}"
state: present
stop_signal: "{{ lookup('dict', container_data).value.stop_signal | default(omit) }}"
stop_timeout: "{{ lookup('dict', container_data).value.stop_grace_period | default(omit) | int }}"
tty: "{{ lookup('dict', container_data).value.tty | default(false) }}"
ulimit: "{{ lookup('dict', container_data).value.ulimit | default(omit) }}"
user: "{{ lookup('dict', container_data).value.user | default(omit) }}"
uts: "{{ lookup('dict', container_data).value.uts | default(omit) }}"
volume: "{{ lookup('dict', container_data).value.volumes | default(omit) }}"
volumes_from: "{{ lookup('dict', container_data).value.volumes_from | default([]) }}"
- name: "Check podman create status"
async_status:
jid: "{{ create_async_result_item.ansible_job_id }}"
loop: "{{ create_async_results.results }}"
loop_control:
loop_var: "create_async_result_item"
register: create_async_poll_results
until: create_async_poll_results.finished
retries: 30
# async and check mode don't work together
when:
- not ansible_check_mode|bool
# This fact will be used in systemd playbook to figure out if whether or not
# a container managed by systemd needs to be restarted
- name: "Create a list of containers which changed"
set_fact:
containers_changed: >-
{{ create_async_results.results | selectattr('changed', 'equalto', true) |
map(attribute='container_data') | list | list_of_keys }}
- name: "Print the list of containers which changed"
debug:
var: containers_changed
when: tripleo_container_manage_debug | bool

View File

@ -0,0 +1,77 @@
---
# Copyright 2019 Red Hat, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
# This playbook is a "best effort" way to remove a container from a host.
# It'll try to remove the healthcheck, service and then container without
# much validation in case things failed in the middle.
- name: "Remove systemd healthcheck for {{ item }}"
block:
- name: "Stop and disable systemd timer for {{ item }}"
systemd:
state: stopped
name: "tripleo_{{ item }}_healthcheck.timer"
enabled: false
ignore_errors: true
- name: "Delete systemd timer file for {{ item }}"
file:
path: "/etc/systemd/system/tripleo_{{ item }}_healthcheck.timer"
state: absent
register: systemd_timer_deleted
- name: "Stop and disable systemd healthcheck for {{ item }}"
systemd:
state: stopped
name: "tripleo_{{ item }}_healthcheck.service"
enabled: false
ignore_errors: true
- name: "Delete systemd healthcheck file for {{ item }}"
file:
path: "/etc/systemd/system/tripleo_{{ item }}_healthcheck.service"
state: absent
register: systemd_healthcheck_deleted
- name: Force systemd to reread configs
systemd:
daemon_reload: true
when: systemd_timer_deleted.changed or systemd_healthcheck_deleted.changed
- name: "Stop and disable systemd service for {{ item }}"
systemd:
state: stopped
name: "tripleo_{{ item }}.service"
enabled: false
ignore_errors: true
- name: "Delete systemd unit file for {{ item }}"
file:
path: "/etc/systemd/system/tripleo_{{ item }}.service"
state: absent
register: systemd_file_deleted
- name: "Remove trailing .requires for {{ item }}"
file:
path: "/etc/systemd/system/tripleo_{{ item }}.requires"
state: absent
register: systemd_requires_deleted
- name: Force systemd to reread configs
systemd:
daemon_reload: true
when: systemd_file_deleted.changed or systemd_requires_deleted.changed
- name: "Remove container {{ item }}"
podman_container:
name: "{{ item }}"
state: absent

View File

@ -0,0 +1,44 @@
---
# Copyright 2019 Red Hat, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
- name: "Check if containers are running before doing exec"
include_tasks: container_running.yml
loop: "{{ batched_container_data | haskey(attribute='action', value='exec') }}"
loop_control:
loop_var: container_exists_data
when: not ansible_check_mode|bool
- name: "Async container exec"
command:
argv: "{{ lookup('dict', container_exec_data).value | container_exec_cmd(cli=tripleo_container_manage_cli) }}"
async: 60
poll: 0
register: exec_async_results
loop: "{{ batched_container_data | haskey(attribute='action', value='exec') }}"
loop_control:
loop_var: container_exec_data
when: not ansible_check_mode|bool
- name: "Check podman exec status"
async_status:
jid: "{{ exec_async_result_item.ansible_job_id }}"
loop: "{{ exec_async_results.results }}"
loop_control:
loop_var: "exec_async_result_item"
register: exec_async_poll_results
until: exec_async_poll_results.finished
retries: 30
when: not ansible_check_mode|bool

View File

@ -0,0 +1,33 @@
---
# Copyright 2019 Red Hat, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
- name: Run containers execs asynchronously
include_tasks: podman/exec.yml
- name: Manage containers asynchronously
include_tasks: podman/create.yml
# We don't want to use async for the systemd tasks or we can have startup
# errors when systemd has to deal with multiple services trying to start
# at the same time. It is more reliable to start them in serial.
- name: Manage container systemd services and healthchecks in serial
include_tasks: podman/systemd.yml
# systemd doesn't have the equivalent of docker unless-stopped.
# Let's force 'always' so containers aren't restarted when stopped by
# systemd, but restarted when in failure.
loop: "{{ batched_container_data | haskey(attribute='restart', value=['always','unless-stopped'], any=True) }}"
loop_control:
loop_var: container_config

View File

@ -0,0 +1,27 @@
---
# Copyright 2019 Red Hat, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
- name: Gather podman infos
podman_container_info: {}
register: podman_containers
when:
- tripleo_container_manage_cli == 'podman'
- name: "Batching items for start_order {{ order }}"
include_tasks: podman/manage.yml
loop: "{{ data | batch(tripleo_container_manage_concurrency) | list }}"
loop_control:
loop_var: batched_container_data

View File

@ -0,0 +1,74 @@
---
# Copyright 2019 Red Hat, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
- name: Set container_name and container_sysd facts
set_fact:
container_sysd_name: "{{ lookup('dict', container_config).key }}"
container_sysd_data: "{{ lookup('dict', container_config).value }}"
- name: "Start systemd service for {{ container_sysd_name }}"
block:
- name: "Remove trailing .requires for {{ container_sysd_name }}"
file:
path: "/etc/systemd/system/tripleo_{{ container_sysd_name }}.requires"
state: absent
- name: "Create systemd unit file for {{ container_sysd_name }} service"
template:
src: systemd-service.j2
dest: "/etc/systemd/system/tripleo_{{ container_sysd_name }}.service"
mode: '0644'
owner: root
group: root
register: systemd_file
- name: "Enable and start systemd service for {{ container_sysd_name }}"
systemd:
# Restart the service if it was already running
state: restarted
name: "tripleo_{{ container_sysd_name }}.service"
enabled: true
daemon_reload: true
when:
- systemd_file is changed or container_sysd_name in containers_changed
- name: "Manage systemd healthcheck for {{ container_sysd_name }}"
when:
- not tripleo_container_manage_healthcheck_disabled
- container_sysd_data.healthcheck is defined
block:
- name: "Create systemd unit file for {{ container_sysd_name }} healthcheck"
template:
src: systemd-healthcheck.j2
dest: "/etc/systemd/system/tripleo_{{ container_sysd_name }}_healthcheck.service"
mode: '0644'
owner: root
group: root
register: systemd_healthcheck
- name: "Create systemd timer for {{ container_sysd_name }} healthcheck"
template:
src: systemd-timer.j2
dest: "/etc/systemd/system/tripleo_{{ container_sysd_name }}_healthcheck.timer"
mode: '0644'
owner: root
group: root
register: systemd_timer
- name: "Enable and start systemd timer for {{ container_sysd_name }}"
systemd:
# Restart the timer if it was already running
state: restarted
name: "tripleo_{{ container_sysd_name }}_healthcheck.timer"
enabled: true
daemon_reload: true
when:
- systemd_healthcheck.changed or systemd_timer.changed

View File

@ -0,0 +1,114 @@
---
# Copyright 2019 Red Hat, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
- name: Check if /etc/sysconfig/podman_drop_in exists
stat:
path: /etc/sysconfig/podman_drop_in
register: podman_drop_in
- name: Set podman_drop_in fact
set_fact:
podman_drop_in: true
when:
- podman_drop_in.stat.exists
- name: Cleanup Paunch services and files
block:
- name: Check if /usr/lib/systemd/system/paunch-container-shutdown.service exists
stat:
path: /usr/lib/systemd/system/paunch-container-shutdown.service
register: paunch_shutdown
- name: Tear-down paunch-container-shutdown
when:
- paunch_shutdown.stat.exists
block:
- name: Allow paunch-container-shutdown to be stopped
lineinfile:
path: /usr/lib/systemd/system/paunch-container-shutdown.service
regexp: '^RefuseManualStop'
line: 'RefuseManualStop=no'
- name: Force systemd to reread configs
systemd:
daemon_reload: true
- name: Disable and stop paunch-container-shutdown service
systemd:
name: paunch-container-shutdown
state: stopped
enabled: false
# TODO(emilien): this task can be removed later when paunch isn't a
# dependency of python-tripleoclient. It'll be replaced by an rpm removal.
- name: "Remove paunch files for systemd"
file:
path: "{{ item }}"
state: absent
loop:
- /usr/libexec/paunch-container-shutdown
- /usr/libexec/paunch-start-podman-container
- /usr/lib/systemd/system/paunch-container-shutdown.service
- /usr/lib/systemd/system-preset/91-paunch-container-shutdown.preset
- name: Create TripleO Container systemd service
block:
- name: "Deploy tripleo-container-shutdown and tripleo-start-podman-container"
copy:
src: "{{ role_path }}/files/{{ item }}"
dest: "/usr/libexec/{{ item }}"
mode: '0700'
owner: root
group: root
loop:
- 'tripleo-container-shutdown'
- 'tripleo-start-podman-container'
- name: "Create /usr/lib/systemd/system/tripleo-container-shutdown.service"
copy:
src: "{{ role_path }}/files/tripleo-container-shutdown-service"
dest: "/usr/lib/systemd/system/tripleo-container-shutdown.service"
mode: '0700'
owner: root
group: root
- name: "Create /usr/lib/systemd/system-preset/91-tripleo-container-shutdown.preset"
copy:
src: "{{ role_path }}/files/91-tripleo-container-shutdown-preset"
dest: "/usr/lib/systemd/system-preset/91-tripleo-container-shutdown.preset"
mode: '0700'
owner: root
group: root
- name: Enable and start tripleo-container-shutdown
systemd:
name: tripleo-container-shutdown
state: started
enabled: true
daemon_reload: true
- name: "Create /usr/lib/systemd/system/netns-placeholder.service"
copy:
src: "{{ role_path }}/files/netns-placeholder-service"
dest: "/usr/lib/systemd/system/netns-placeholder.service"
mode: '0700'
owner: root
group: root
- name: "Create /usr/lib/systemd/system-preset/91-netns-placeholder.preset"
copy:
src: "{{ role_path }}/files/91-netns-placeholder-preset"
dest: "/usr/lib/systemd/system-preset/91-netns-placeholder.preset"
mode: '0700'
owner: root
group: root
- name: Enable and start netns-placeholder
systemd:
name: netns-placeholder
state: started
enabled: true
daemon_reload: true

View File

@ -0,0 +1,10 @@
[Unit]
Description=tripleo_{{ container_sysd_name }} healthcheck
After=tripleo-container-shutdown.service tripleo_{{ container_sysd_name }}.service
Requisite=tripleo_{{ container_sysd_name }}.service
[Service]
Type=oneshot
ExecStart=/usr/bin/podman exec {{ container_sysd_name }} {{ container_sysd_data.healthcheck.test }}
SyslogIdentifier=healthcheck_{{ container_sysd_name }}
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,23 @@
[Unit]
Description={{ container_sysd_name }} container
After=tripleo-container-shutdown.service
Wants={{ container_sysd_data.depends_on | default([]) | join(',') }}
[Service]
Restart=always
{% if container_sysd_data.depends_on is defined and (container_sysd_data.depends_on | length > 0) and podman_drop_in | default('false') %}
ExecStart=/usr/libexec/tripleo-start-podman-container {{ container_sysd_name }}
{% else %}
ExecStart=/usr/bin/podman start {{ container_sysd_name }}
{% endif %}
ExecReload=/usr/bin/podman kill --signal HUP {{ container_sysd_name }}
ExecStop=/usr/bin/podman stop -t {{ container_sysd_data.stop_grace_period | default(10) | int }} {{ container_sysd_name }}
KillMode=none
Type=forking
PIDFile=/var/run/{{ container_sysd_name }}.pid
{% if container_sysd_data.systemd_exec_flags is defined %}
{% for s_flag, s_value in container_sysd_data.systemd_exec_flags.items() %}
{{ s_flag }}={{ s_value }}
{% endfor %}
{% endif %}
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,9 @@
[Unit]
Description=tripleo_{{ container_sysd_name }} container healthcheck
PartOf=tripleo_{{ container_sysd_name }}.service
[Timer]
OnActiveSec=120
OnUnitActiveSec={{ container_sysd_data.check_interval | default(60) }}
RandomizedDelaySec={{ 45 if container_sysd_data.check_interval is not defined else (container_sysd_data.check_interval * 3 / 4) | int | abs }}
[Install]
WantedBy=timers.target

View File

View File

@ -0,0 +1,21 @@
# Copyright 2019 Red Hat, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslotest import base
class TestCase(base.BaseTestCase):
"""Test case base class for all unit tests."""

View File

@ -0,0 +1,376 @@
# Copyright 2019 Red Hat, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from tripleo_ansible.ansible_plugins.filter import helpers
from tripleo_ansible.tests import base as tests_base
class TestHelperFilters(tests_base.TestCase):
def setUp(self):
super(TestHelperFilters, self).setUp()
self.filters = helpers.FilterModule()
def test_subsort(self):
dict = {
'keystone': {
'start_order': 1,
'image': 'quay.io/tripleo/keystone'
},
'haproxy': {
'image': 'quay.io/tripleo/haproxy'
},
'mysql': {
'start_order': 0,
'image': 'quay.io/tripleo/mysql'
}
}
expected_ordered_dict = {
0: [
{'haproxy': {
'image': 'quay.io/tripleo/haproxy',
'start_order': 0
}},
{'mysql': {
'image': 'quay.io/tripleo/mysql',
'start_order': 0
}}
],
1: [
{'keystone': {
'image': 'quay.io/tripleo/keystone',
'start_order': 1
}}
]
}
result = self.filters.subsort(dict_to_sort=dict,
attribute='start_order')
self.assertEqual(result, expected_ordered_dict)
def test_subsort_with_null_value(self):
dict = {
'keystone': {
'start_order': 1,
'image': 'quay.io/tripleo/keystone'
},
'haproxy': {
'image': 'quay.io/tripleo/haproxy'
},
'mysql': {
'start_order': 0,
'image': 'quay.io/tripleo/mysql'
}
}
expected_ordered_dict = {
0: [
{'mysql': {
'image': 'quay.io/tripleo/mysql',
'start_order': 0
}}
],
1: [
{'keystone': {
'image': 'quay.io/tripleo/keystone',
'start_order': 1
}}
],
5: [
{'haproxy': {
'image': 'quay.io/tripleo/haproxy',
'start_order': 5
}}
]
}
result = self.filters.subsort(dict_to_sort=dict,
attribute='start_order', null_value=5)
self.assertEqual(result, expected_ordered_dict)
def test_singledict(self):
list = [
{
'keystone': {
'start_order': 1,
'image': 'quay.io/tripleo/keystone'
},
},
{
'mysql': {
'start_order': 0,
'image': 'quay.io/tripleo/mysql'
}
}
]
expected_dict = {
'keystone': {
'start_order': 1,
'image': 'quay.io/tripleo/keystone'
},
'mysql': {
'start_order': 0,
'image': 'quay.io/tripleo/mysql'
}
}
result = self.filters.singledict(list)
self.assertEqual(result, expected_dict)
def test_list_of_keys(self):
keys = [
{
'foo1': 'bar1'
},
{
'foo2': 'bar2'
},
]
expected_list = ['foo1', 'foo2']
result = self.filters.list_of_keys(keys)
self.assertEqual(result, expected_list)
def test_haskey(self):
data = [
{
'keystone': {
'start_order': 1,
'image': 'quay.io/tripleo/keystone',
'restart': 'always'
},
},
{
'mysql': {
'start_order': 0,
'image': 'quay.io/tripleo/mysql'
}
}
]
expected_list = [
{
'keystone': {
'start_order': 1,
'image': 'quay.io/tripleo/keystone',
'restart': 'always'
},
}
]
result = self.filters.haskey(batched_container_data=data,
attribute='restart', value='always')
self.assertEqual(result, expected_list)
def test_haskey_reverse(self):
data = [
{
'keystone': {
'start_order': 1,
'image': 'quay.io/tripleo/keystone',
'restart': 'always'
},
},
{
'mysql': {
'start_order': 0,
'image': 'quay.io/tripleo/mysql'
}
}
]
expected_list = [
{
'mysql': {
'start_order': 0,
'image': 'quay.io/tripleo/mysql'
},
}
]
result = self.filters.haskey(batched_container_data=data,
attribute='restart',
value='always',
reverse=True)
self.assertEqual(result, expected_list)
def test_haskey_any(self):
data = [
{
'keystone': {
'start_order': 1,
'image': 'quay.io/tripleo/keystone',
'restart': 'always'
},
},
{
'mysql': {
'start_order': 0,
'image': 'quay.io/tripleo/mysql'
}
}
]
expected_list = [
{
'keystone': {
'start_order': 1,
'image': 'quay.io/tripleo/keystone',
'restart': 'always'
},
}
]
result = self.filters.haskey(batched_container_data=data,
attribute='restart',
any=True)
self.assertEqual(result, expected_list)
def test_haskey_any_reverse(self):
data = [
{
'keystone': {
'start_order': 1,
'image': 'quay.io/tripleo/keystone',
'restart': 'always'
},
},
{
'mysql': {
'start_order': 0,
'image': 'quay.io/tripleo/mysql'
}
}
]
expected_list = [
{
'mysql': {
'start_order': 0,
'image': 'quay.io/tripleo/mysql'
},
}
]
result = self.filters.haskey(batched_container_data=data,
attribute='restart',
reverse=True,
any=True)
self.assertEqual(result, expected_list)
def test_needs_delete(self):
data = [
{
'Name': 'mysql',
'Config': {
'Labels': {
'config_id': 'dontdeleteme',
'managed_by': 'triple_ansible',
}
}
},
{
'Name': 'rabbitmq',
'Config': {
'Labels': {
'managed_by': 'tripleo_ansible',
'config_id': 'tripleo_step1',
'container_name': 'rabbitmq',
'name': 'rabbitmq',
}
}
},
{
'Name': 'swift',
'Config': {
'Labels': {
'managed_by': 'tripleo_ansible',
'config_id': 'tripleo_step1',
'container_name': 'swift',
'name': 'swift',
'config_data': "{'foo': 'bar'}",
}
}
},
{
'Name': 'heat',
'Config': {
'Labels': {
'managed_by': 'tripleo_ansible',
'config_id': 'tripleo_step1',
'container_name': 'heat',
'name': 'heat',
'config_data': "{'start_order': 0}",
}
}
},
{
'Name': 'haproxy',
'Config': {
'Labels': {
'managed_by': 'tripleo_ansible',
'config_id': 'tripleo_step1',
}
}
},
{
'Name': 'tripleo',
'Config': {
'Labels': {
'foo': 'bar'
}
}
},
{
'Name': 'none_tripleo',
'Config': {
'Labels': None,
}
},
]
config = {
# we don't want that container to be touched: no restart
'mysql': '',
# container has no Config, therefore no Labels: restart needed
'rabbitmq': '',
# container has no config_data: restart needed
'haproxy': '',
# container isn't part of config_id: no restart
'tripleo': '',
# container isn't in container_infos but not part of config_id:
# no restart.
'doesnt_exist': '',
# config_data didn't change: no restart
'swift': {'foo': 'bar'},
# config_data changed: restart needed
'heat': {'start_order': 1},
}
expected_list = ['rabbitmq', 'haproxy', 'heat']
result = self.filters.needs_delete(container_infos=data,
config=config,
config_id='tripleo_step1')
self.assertEqual(result, expected_list)
def test_container_exec_cmd(self):
data = {
"action": "exec",
"environment": {
"OS_BOOTSTRAP_PASSWORD": "IH7PdaZc5DozbmunSTjMa7",
"KOLLA_BOOTSTRAP": True
},
"start_order": 3,
"command": [
"keystone",
"/usr/bin/bootstrap_host_exec",
"keystone",
"keystone-manage",
"bootstrap"
],
"user": "root"
}
expected_cmd = ['podman', 'exec', '--user=root',
'--env=KOLLA_BOOTSTRAP=True',
'--env=OS_BOOTSTRAP_PASSWORD=IH7PdaZc5DozbmunSTjMa7',
'keystone', '/usr/bin/bootstrap_host_exec',
'keystone', 'keystone-manage', 'bootstrap']
result = self.filters.container_exec_cmd(data=data)
self.assertEqual(result, expected_cmd)

View File

@ -3,6 +3,7 @@
templates:
- tripleo-ansible-molecule-jobs
- release-notes-jobs-python3
- openstack-python3-ussuri-jobs
check:
jobs:
- openstack-tox-linters

View File

@ -38,6 +38,8 @@
- tripleo-ansible-centos-7-molecule-backup-and-restore
- tripleo-ansible-centos-7-molecule-tripleo-packages
- tripleo-ansible-centos-7-molecule-tripleo-hosts-entries
- tripleo-ansible-centos-7-molecule-tripleo-container-manage
- tripleo-ansible-centos-7-molecule-tripleo-modules
gate:
jobs:
- tripleo-ansible-centos-7-molecule-aide
@ -76,6 +78,8 @@
- tripleo-ansible-centos-7-molecule-backup-and-restore
- tripleo-ansible-centos-7-molecule-tripleo-packages
- tripleo-ansible-centos-7-molecule-tripleo-hosts-entries
- tripleo-ansible-centos-7-molecule-tripleo-container-manage
- tripleo-ansible-centos-7-molecule-tripleo-modules
name: tripleo-ansible-molecule-jobs
- job:
files:
@ -338,3 +342,18 @@
parent: tripleo-ansible-centos-7-base
vars:
tripleo_role_name: tripleo-hosts-entries
- job:
files:
- ^tripleo_ansible/roles/tripleo-container-manage/.*
name: tripleo-ansible-centos-7-molecule-tripleo-container-manage
parent: tripleo-ansible-centos-7-base
vars:
tripleo_role_name: tripleo-container-manage
- job:
files:
- ^tripleo_ansible/ansible_plugins/.*$
- ^tox.ini
- ^molecule-requirements.txt
name: tripleo-ansible-centos-7-molecule-tripleo-modules
parent: tripleo-ansible-centos-7-base

View File

@ -4,10 +4,21 @@
environment:
ANSIBLE_LOG_PATH: "{{ ansible_user_dir }}/zuul-output/logs/ansible-execution.log"
pre_tasks:
- name: Set project path fact
set_fact:
tripleo_ansible_project_path: "{{ ansible_user_dir }}/{{ zuul.project.src_dir }}"
- name: Set role or plugin path fact
set_fact:
triple_ansible_testdir: "{{ tripleo_ansible_project_path }}/tripleo_ansible/roles/{{ tripleo_role_name }}"
when: tripleo_role_name is defined and tripleo_role_name
- name: Set role or plugin path fact
set_fact:
triple_ansible_testdir: "{{ tripleo_ansible_project_path }}/tripleo_ansible/ansible_plugins/tests"
when: tripleo_role_name is not defined
- name: Set action plugin path fact
set_fact:
tripleo_action_plugins_paths:
@ -25,7 +36,7 @@
--ansible-args='{{ tripleo_job_ansible_args | default('') }}' \
{{ tripleo_ansible_project_path }}/tests/test_molecule.py
args:
chdir: "{{ tripleo_ansible_project_path }}/tripleo_ansible/roles/{{ tripleo_role_name }}"
chdir: "{{ triple_ansible_testdir }}"
executable: /bin/bash
environment:
ANSIBLE_ACTION_PLUGINS: "{{ tripleo_action_plugins_paths | join(':') }}"