summaryrefslogtreecommitdiffstats
path: root/collections-debian-merged/ansible_collections/containers/podman/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'collections-debian-merged/ansible_collections/containers/podman/plugins')
-rw-r--r--collections-debian-merged/ansible_collections/containers/podman/plugins/connection/__init__.py0
-rw-r--r--collections-debian-merged/ansible_collections/containers/podman/plugins/connection/buildah.py202
-rw-r--r--collections-debian-merged/ansible_collections/containers/podman/plugins/connection/podman.py224
-rw-r--r--collections-debian-merged/ansible_collections/containers/podman/plugins/module_utils/__init__.py0
-rw-r--r--collections-debian-merged/ansible_collections/containers/podman/plugins/module_utils/podman/__init__.py0
-rw-r--r--collections-debian-merged/ansible_collections/containers/podman/plugins/module_utils/podman/common.py28
-rw-r--r--collections-debian-merged/ansible_collections/containers/podman/plugins/module_utils/podman/podman_container_lib.py1479
-rw-r--r--collections-debian-merged/ansible_collections/containers/podman/plugins/module_utils/podman/podman_pod_lib.py736
-rw-r--r--collections-debian-merged/ansible_collections/containers/podman/plugins/modules/__init__.py0
-rw-r--r--collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_container.py916
-rw-r--r--collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_container_info.py405
-rw-r--r--collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_containers.py162
-rw-r--r--collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_image.py774
-rw-r--r--collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_image_info.py234
-rw-r--r--collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_login_info.py117
-rw-r--r--collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_logout.py154
-rw-r--r--collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_network.py614
-rw-r--r--collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_network_info.py138
-rw-r--r--collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_pod.py232
-rw-r--r--collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_pod_info.py145
-rw-r--r--collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_volume.py477
-rw-r--r--collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_volume_info.py100
22 files changed, 7137 insertions, 0 deletions
diff --git a/collections-debian-merged/ansible_collections/containers/podman/plugins/connection/__init__.py b/collections-debian-merged/ansible_collections/containers/podman/plugins/connection/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/containers/podman/plugins/connection/__init__.py
diff --git a/collections-debian-merged/ansible_collections/containers/podman/plugins/connection/buildah.py b/collections-debian-merged/ansible_collections/containers/podman/plugins/connection/buildah.py
new file mode 100644
index 00000000..748c0ec7
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/containers/podman/plugins/connection/buildah.py
@@ -0,0 +1,202 @@
+# Based on the docker connection plugin
+# Copyright (c) 2017 Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+#
+# Connection plugin for building container images using buildah tool
+# https://github.com/projectatomic/buildah
+#
+# Written by: Tomas Tomecek (https://github.com/TomasTomecek)
+
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+
+DOCUMENTATION = '''
+ connection: buildah
+ short_description: Interact with an existing buildah container
+ description:
+ - Run commands or put/fetch files to an existing container using buildah tool.
+ author: Tomas Tomecek (ttomecek@redhat.com)
+ options:
+ remote_addr:
+ description:
+ - The ID of the container you want to access.
+ default: inventory_hostname
+ vars:
+ - name: ansible_host
+# keyword:
+# - name: hosts
+ remote_user:
+ description:
+ - User specified via name or ID which is used to execute commands inside the container.
+ ini:
+ - section: defaults
+ key: remote_user
+ env:
+ - name: ANSIBLE_REMOTE_USER
+ vars:
+ - name: ansible_user
+# keyword:
+# - name: remote_user
+'''
+
+import os
+import shlex
+import shutil
+import subprocess
+
+from ansible.errors import AnsibleError
+from ansible.module_utils._text import to_bytes, to_native, to_text
+from ansible.plugins.connection import ConnectionBase, ensure_connect
+from ansible.utils.display import Display
+
+display = Display()
+
+
+# this _has to be_ named Connection
+class Connection(ConnectionBase):
+ """
+ This is a connection plugin for buildah: it uses buildah binary to interact with the containers
+ """
+
+ # String used to identify this Connection class from other classes
+ transport = 'containers.podman.buildah'
+ has_pipelining = True
+
+ def __init__(self, play_context, new_stdin, *args, **kwargs):
+ super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs)
+
+ self._container_id = self._play_context.remote_addr
+ self._connected = False
+ # container filesystem will be mounted here on host
+ self._mount_point = None
+ # `buildah inspect` doesn't contain info about what the default user is -- if it's not
+ # set, it's empty
+ self.user = self._play_context.remote_user
+ display.vvvv("Using buildah connection from collection")
+
+ def _set_user(self):
+ self._buildah(b"config", [b"--user=" + to_bytes(self.user, errors='surrogate_or_strict')])
+
+ def _buildah(self, cmd, cmd_args=None, in_data=None, outfile_stdout=None):
+ """
+ run buildah executable
+
+ :param cmd: buildah's command to execute (str)
+ :param cmd_args: list of arguments to pass to the command (list of str/bytes)
+ :param in_data: data passed to buildah's stdin
+ :param outfile_stdout: file for writing STDOUT to
+ :return: return code, stdout, stderr
+ """
+ buildah_exec = 'buildah'
+ local_cmd = [buildah_exec]
+
+ if isinstance(cmd, str):
+ local_cmd.append(cmd)
+ else:
+ local_cmd.extend(cmd)
+ if self.user and self.user != 'root':
+ if cmd == 'run':
+ local_cmd.extend(("--user", self.user))
+ elif cmd == 'copy':
+ local_cmd.extend(("--chown", self.user))
+ local_cmd.append(self._container_id)
+
+ if cmd_args:
+ if isinstance(cmd_args, str):
+ local_cmd.append(cmd_args)
+ else:
+ local_cmd.extend(cmd_args)
+
+ local_cmd = [to_bytes(i, errors='surrogate_or_strict')
+ for i in local_cmd]
+
+ display.vvv("RUN %s" % (local_cmd,), host=self._container_id)
+ if outfile_stdout:
+ stdout_fd = open(outfile_stdout, "wb")
+ else:
+ stdout_fd = subprocess.PIPE
+ p = subprocess.Popen(local_cmd, shell=False, stdin=subprocess.PIPE,
+ stdout=stdout_fd, stderr=subprocess.PIPE)
+
+ stdout, stderr = p.communicate(input=in_data)
+ display.vvvv("STDOUT %s" % to_text(stdout))
+ display.vvvv("STDERR %s" % to_text(stderr))
+ display.vvvv("RC CODE %s" % p.returncode)
+ stdout = to_bytes(stdout, errors='surrogate_or_strict')
+ stderr = to_bytes(stderr, errors='surrogate_or_strict')
+ return p.returncode, stdout, stderr
+
+ def _connect(self):
+ """
+ no persistent connection is being maintained, mount container's filesystem
+ so we can easily access it
+ """
+ super(Connection, self)._connect()
+ rc, self._mount_point, stderr = self._buildah("mount")
+ if rc != 0:
+ display.v("Failed to mount container %s: %s" % (self._container_id, stderr.strip()))
+ else:
+ self._mount_point = self._mount_point.strip() + to_bytes(os.path.sep, errors='surrogate_or_strict')
+ display.vvvv("MOUNTPOINT %s RC %s STDERR %r" % (self._mount_point, rc, stderr))
+ self._connected = True
+
+ @ensure_connect
+ def exec_command(self, cmd, in_data=None, sudoable=False):
+ """ run specified command in a running OCI container using buildah """
+ super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable)
+
+ # shlex.split has a bug with text strings on Python-2.6 and can only handle text strings on Python-3
+ cmd_args_list = shlex.split(to_native(cmd, errors='surrogate_or_strict'))
+
+ rc, stdout, stderr = self._buildah("run", cmd_args_list, in_data)
+
+ display.vvvv("STDOUT %r\nSTDERR %r" % (stderr, stderr))
+ return rc, stdout, stderr
+
+ def put_file(self, in_path, out_path):
+ """ Place a local file located in 'in_path' inside container at 'out_path' """
+ super(Connection, self).put_file(in_path, out_path)
+ display.vvv("PUT %s TO %s" % (in_path, out_path), host=self._container_id)
+ if not self._mount_point or self.user:
+ rc, stdout, stderr = self._buildah(
+ "copy", [in_path, out_path])
+ if rc != 0:
+ raise AnsibleError(
+ "Failed to copy file from %s to %s in container %s\n%s" % (
+ in_path, out_path, self._container_id, stderr)
+ )
+ else:
+ real_out_path = self._mount_point + to_bytes(out_path, errors='surrogate_or_strict')
+ shutil.copyfile(
+ to_bytes(in_path, errors='surrogate_or_strict'),
+ to_bytes(real_out_path, errors='surrogate_or_strict')
+ )
+
+ def fetch_file(self, in_path, out_path):
+ """ obtain file specified via 'in_path' from the container and place it at 'out_path' """
+ super(Connection, self).fetch_file(in_path, out_path)
+ display.vvv("FETCH %s TO %s" %
+ (in_path, out_path), host=self._container_id)
+ if not self._mount_point:
+ rc, stdout, stderr = self._buildah(
+ "run",
+ ["cat", to_bytes(in_path, errors='surrogate_or_strict')],
+ outfile_stdout=out_path)
+ if rc != 0:
+ raise AnsibleError("Failed to fetch file from %s to %s from container %s\n%s" % (
+ in_path, out_path, self._container_id, stderr))
+ else:
+ real_in_path = self._mount_point + \
+ to_bytes(in_path, errors='surrogate_or_strict')
+ shutil.copyfile(
+ to_bytes(real_in_path, errors='surrogate_or_strict'),
+ to_bytes(out_path, errors='surrogate_or_strict')
+ )
+
+ def close(self):
+ """ unmount container's filesystem """
+ super(Connection, self).close()
+ rc, stdout, stderr = self._buildah("umount")
+ display.vvvv("RC %s STDOUT %r STDERR %r" % (rc, stdout, stderr))
+ self._connected = False
diff --git a/collections-debian-merged/ansible_collections/containers/podman/plugins/connection/podman.py b/collections-debian-merged/ansible_collections/containers/podman/plugins/connection/podman.py
new file mode 100644
index 00000000..2e46bd9c
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/containers/podman/plugins/connection/podman.py
@@ -0,0 +1,224 @@
+# Based on the buildah connection plugin
+# Copyright (c) 2018 Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+#
+# Connection plugin to interact with existing podman containers.
+# https://github.com/containers/libpod
+#
+# Written by: Tomas Tomecek (https://github.com/TomasTomecek)
+
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import distutils.spawn
+import os
+import shlex
+import shutil
+import subprocess
+
+from ansible.errors import AnsibleError
+from ansible.module_utils._text import to_bytes, to_native
+from ansible.plugins.connection import ConnectionBase, ensure_connect
+from ansible.utils.display import Display
+
+display = Display()
+
+
+DOCUMENTATION = '''
+ author: Tomas Tomecek (ttomecek@redhat.com)
+ connection: podman
+ short_description: Interact with an existing podman container
+ description:
+ - Run commands or put/fetch files to an existing container using podman tool.
+ options:
+ remote_addr:
+ description:
+ - The ID of the container you want to access.
+ default: inventory_hostname
+ vars:
+ - name: ansible_host
+ remote_user:
+ description:
+ - User specified via name or UID which is used to execute commands inside the container. If you
+ specify the user via UID, you must set C(ANSIBLE_REMOTE_TMP) to a path that exits
+ inside the container and is writable by Ansible.
+ ini:
+ - section: defaults
+ key: remote_user
+ env:
+ - name: ANSIBLE_REMOTE_USER
+ vars:
+ - name: ansible_user
+ podman_extra_args:
+ description:
+ - Extra arguments to pass to the podman command line.
+ default: ''
+ ini:
+ - section: defaults
+ key: podman_extra_args
+ vars:
+ - name: ansible_podman_extra_args
+ env:
+ - name: ANSIBLE_PODMAN_EXTRA_ARGS
+ podman_executable:
+ description:
+ - Executable for podman command.
+ default: podman
+ vars:
+ - name: ansible_podman_executable
+ env:
+ - name: ANSIBLE_PODMAN_EXECUTABLE
+'''
+
+
+# this _has to be_ named Connection
+class Connection(ConnectionBase):
+ """
+ This is a connection plugin for podman. It uses podman binary to interact with the containers
+ """
+
+ # String used to identify this Connection class from other classes
+ transport = 'containers.podman.podman'
+ has_pipelining = True
+
+ def __init__(self, play_context, new_stdin, *args, **kwargs):
+ super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs)
+
+ self._container_id = self._play_context.remote_addr
+ self._connected = False
+ # container filesystem will be mounted here on host
+ self._mount_point = None
+ self.user = self._play_context.remote_user
+ display.vvvv("Using podman connection from collection")
+
+ def _podman(self, cmd, cmd_args=None, in_data=None, use_container_id=True):
+ """
+ run podman executable
+
+ :param cmd: podman's command to execute (str or list)
+ :param cmd_args: list of arguments to pass to the command (list of str/bytes)
+ :param in_data: data passed to podman's stdin
+ :return: return code, stdout, stderr
+ """
+ podman_exec = self.get_option('podman_executable')
+ podman_cmd = distutils.spawn.find_executable(podman_exec)
+ if not podman_cmd:
+ raise AnsibleError("%s command not found in PATH" % podman_exec)
+ local_cmd = [podman_cmd]
+ if self.get_option('podman_extra_args'):
+ local_cmd += shlex.split(
+ to_native(
+ self.get_option('podman_extra_args'),
+ errors='surrogate_or_strict'))
+ if isinstance(cmd, str):
+ local_cmd.append(cmd)
+ else:
+ local_cmd.extend(cmd)
+
+ if use_container_id:
+ local_cmd.append(self._container_id)
+ if cmd_args:
+ local_cmd += cmd_args
+ local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd]
+
+ display.vvv("RUN %s" % (local_cmd,), host=self._container_id)
+ p = subprocess.Popen(local_cmd, shell=False, stdin=subprocess.PIPE,
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+
+ stdout, stderr = p.communicate(input=in_data)
+ display.vvvvv("STDOUT %s" % stdout)
+ display.vvvvv("STDERR %s" % stderr)
+ display.vvvvv("RC CODE %s" % p.returncode)
+ stdout = to_bytes(stdout, errors='surrogate_or_strict')
+ stderr = to_bytes(stderr, errors='surrogate_or_strict')
+ return p.returncode, stdout, stderr
+
+ def _connect(self):
+ """
+ no persistent connection is being maintained, mount container's filesystem
+ so we can easily access it
+ """
+ super(Connection, self)._connect()
+ rc, self._mount_point, stderr = self._podman("mount")
+ if rc != 0:
+ display.vvvv("Failed to mount container %s: %s" % (self._container_id, stderr.strip()))
+ elif not os.listdir(self._mount_point.strip()):
+ display.vvvv("Failed to mount container with CGroups2: empty dir %s" % self._mount_point.strip())
+ self._mount_point = None
+ else:
+ self._mount_point = self._mount_point.strip()
+ display.vvvvv("MOUNTPOINT %s RC %s STDERR %r" % (self._mount_point, rc, stderr))
+ self._connected = True
+
+ @ensure_connect
+ def exec_command(self, cmd, in_data=None, sudoable=False):
+ """ run specified command in a running OCI container using podman """
+ super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable)
+
+ # shlex.split has a bug with text strings on Python-2.6 and can only handle text strings on Python-3
+ cmd_args_list = shlex.split(to_native(cmd, errors='surrogate_or_strict'))
+ exec_args_list = ["exec"]
+ if self.user:
+ exec_args_list.extend(("--user", self.user))
+
+ rc, stdout, stderr = self._podman(exec_args_list, cmd_args_list, in_data)
+
+ display.vvvvv("STDOUT %r STDERR %r" % (stderr, stderr))
+ return rc, stdout, stderr
+
+ def put_file(self, in_path, out_path):
+ """ Place a local file located in 'in_path' inside container at 'out_path' """
+ super(Connection, self).put_file(in_path, out_path)
+ display.vvv("PUT %s TO %s" % (in_path, out_path), host=self._container_id)
+ if not self._mount_point or self.user:
+ rc, stdout, stderr = self._podman(
+ "cp", [in_path, self._container_id + ":" + out_path], use_container_id=False
+ )
+ if rc != 0:
+ rc, stdout, stderr = self._podman(
+ "cp", ["--pause=false", in_path, self._container_id + ":" + out_path], use_container_id=False
+ )
+ if rc != 0:
+ raise AnsibleError(
+ "Failed to copy file from %s to %s in container %s\n%s" % (
+ in_path, out_path, self._container_id, stderr)
+ )
+ if self.user:
+ rc, stdout, stderr = self._podman(
+ "exec", ["chown", self.user, out_path])
+ if rc != 0:
+ raise AnsibleError(
+ "Failed to chown file %s for user %s in container %s\n%s" % (
+ out_path, self.user, self._container_id, stderr)
+ )
+ else:
+ real_out_path = self._mount_point + to_bytes(out_path, errors='surrogate_or_strict')
+ shutil.copyfile(
+ to_bytes(in_path, errors='surrogate_or_strict'),
+ to_bytes(real_out_path, errors='surrogate_or_strict')
+ )
+
+ def fetch_file(self, in_path, out_path):
+ """ obtain file specified via 'in_path' from the container and place it at 'out_path' """
+ super(Connection, self).fetch_file(in_path, out_path)
+ display.vvv("FETCH %s TO %s" % (in_path, out_path), host=self._container_id)
+ if not self._mount_point:
+ rc, stdout, stderr = self._podman(
+ "cp", [self._container_id + ":" + in_path, out_path], use_container_id=False)
+ if rc != 0:
+ raise AnsibleError("Failed to fetch file from %s to %s from container %s\n%s" % (
+ in_path, out_path, self._container_id, stderr))
+ else:
+ real_in_path = self._mount_point + to_bytes(in_path, errors='surrogate_or_strict')
+ shutil.copyfile(
+ to_bytes(real_in_path, errors='surrogate_or_strict'),
+ to_bytes(out_path, errors='surrogate_or_strict')
+ )
+
+ def close(self):
+ """ unmount container's filesystem """
+ super(Connection, self).close()
+ # we actually don't need to unmount since the container is mounted anyway
+ # rc, stdout, stderr = self._podman("umount")
+ # display.vvvvv("RC %s STDOUT %r STDERR %r" % (rc, stdout, stderr))
+ self._connected = False
diff --git a/collections-debian-merged/ansible_collections/containers/podman/plugins/module_utils/__init__.py b/collections-debian-merged/ansible_collections/containers/podman/plugins/module_utils/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/containers/podman/plugins/module_utils/__init__.py
diff --git a/collections-debian-merged/ansible_collections/containers/podman/plugins/module_utils/podman/__init__.py b/collections-debian-merged/ansible_collections/containers/podman/plugins/module_utils/podman/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/containers/podman/plugins/module_utils/podman/__init__.py
diff --git a/collections-debian-merged/ansible_collections/containers/podman/plugins/module_utils/podman/common.py b/collections-debian-merged/ansible_collections/containers/podman/plugins/module_utils/podman/common.py
new file mode 100644
index 00000000..d7b0ed79
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/containers/podman/plugins/module_utils/podman/common.py
@@ -0,0 +1,28 @@
+#!/usr/bin/python
+# Copyright (c) 2020 Red Hat
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+def run_podman_command(module, executable='podman', args=None, expected_rc=0, ignore_errors=False):
+ if not isinstance(executable, list):
+ command = [executable]
+ if args is not None:
+ command.extend(args)
+ rc, out, err = module.run_command(command)
+ if not ignore_errors and rc != expected_rc:
+ module.fail_json(
+ msg='Failed to run {command} {args}: {err}'.format(
+ command=command, args=args, err=err))
+ return rc, out, err
+
+
+def lower_keys(x):
+ if isinstance(x, list):
+ return [lower_keys(v) for v in x]
+ elif isinstance(x, dict):
+ return dict((k.lower(), lower_keys(v)) for k, v in x.items())
+ else:
+ return x
diff --git a/collections-debian-merged/ansible_collections/containers/podman/plugins/module_utils/podman/podman_container_lib.py b/collections-debian-merged/ansible_collections/containers/podman/plugins/module_utils/podman/podman_container_lib.py
new file mode 100644
index 00000000..b9fdc431
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/containers/podman/plugins/module_utils/podman/podman_container_lib.py
@@ -0,0 +1,1479 @@
+from __future__ import (absolute_import, division, print_function)
+import json # noqa: F402
+from distutils.version import LooseVersion # noqa: F402
+
+from ansible.module_utils._text import to_bytes, to_native # noqa: F402
+from ansible_collections.containers.podman.plugins.module_utils.podman.common import lower_keys
+
+__metaclass__ = type
+
+ARGUMENTS_SPEC_CONTAINER = dict(
+ name=dict(required=True, type='str'),
+ executable=dict(default='podman', type='str'),
+ state=dict(type='str', default='started', choices=[
+ 'absent', 'present', 'stopped', 'started']),
+ image=dict(type='str'),
+ annotation=dict(type='dict'),
+ authfile=dict(type='path'),
+ blkio_weight=dict(type='int'),
+ blkio_weight_device=dict(type='dict'),
+ cap_add=dict(type='list', elements='str', aliases=['capabilities']),
+ cap_drop=dict(type='list', elements='str'),
+ cgroup_parent=dict(type='path'),
+ cgroupns=dict(type='str'),
+ cgroups=dict(type='str', choices=['default', 'disabled']),
+ cidfile=dict(type='path'),
+ cmd_args=dict(type='list', elements='str'),
+ conmon_pidfile=dict(type='path'),
+ command=dict(type='raw'),
+ cpu_period=dict(type='int'),
+ cpu_rt_period=dict(type='int'),
+ cpu_rt_runtime=dict(type='int'),
+ cpu_shares=dict(type='int'),
+ cpus=dict(type='str'),
+ cpuset_cpus=dict(type='str'),
+ cpuset_mems=dict(type='str'),
+ detach=dict(type='bool', default=True),
+ debug=dict(type='bool', default=False),
+ detach_keys=dict(type='str'),
+ device=dict(type='list', elements='str'),
+ device_read_bps=dict(type='list'),
+ device_read_iops=dict(type='list'),
+ device_write_bps=dict(type='list'),
+ device_write_iops=dict(type='list'),
+ dns=dict(type='list', elements='str', aliases=['dns_servers']),
+ dns_option=dict(type='str', aliases=['dns_opts']),
+ dns_search=dict(type='str', aliases=['dns_search_domains']),
+ entrypoint=dict(type='str'),
+ env=dict(type='dict'),
+ env_file=dict(type='path'),
+ env_host=dict(type='bool'),
+ etc_hosts=dict(type='dict', aliases=['add_hosts']),
+ expose=dict(type='list', elements='str', aliases=[
+ 'exposed', 'exposed_ports']),
+ force_restart=dict(type='bool', default=False,
+ aliases=['restart']),
+ gidmap=dict(type='list', elements='str'),
+ group_add=dict(type='list', aliases=['groups']),
+ healthcheck=dict(type='str'),
+ healthcheck_interval=dict(type='str'),
+ healthcheck_retries=dict(type='int'),
+ healthcheck_start_period=dict(type='str'),
+ healthcheck_timeout=dict(type='str'),
+ hostname=dict(type='str'),
+ http_proxy=dict(type='bool'),
+ image_volume=dict(type='str', choices=['bind', 'tmpfs', 'ignore']),
+ image_strict=dict(type='bool', default=False),
+ init=dict(type='bool'),
+ init_path=dict(type='str'),
+ interactive=dict(type='bool'),
+ ip=dict(type='str'),
+ ipc=dict(type='str', aliases=['ipc_mode']),
+ kernel_memory=dict(type='str'),
+ label=dict(type='dict', aliases=['labels']),
+ label_file=dict(type='str'),
+ log_driver=dict(type='str', choices=[
+ 'k8s-file', 'journald', 'json-file']),
+ log_level=dict(
+ type='str',
+ choices=["debug", "info", "warn", "error", "fatal", "panic"]),
+ log_opt=dict(type='dict', aliases=['log_options'],
+ options=dict(
+ max_size=dict(type='str'),
+ path=dict(type='str'),
+ tag=dict(type='str'))),
+ mac_address=dict(type='str'),
+ memory=dict(type='str'),
+ memory_reservation=dict(type='str'),
+ memory_swap=dict(type='str'),
+ memory_swappiness=dict(type='int'),
+ mount=dict(type='str'),
+ network=dict(type='list', elements='str', aliases=['net', 'network_mode']),
+ no_hosts=dict(type='bool'),
+ oom_kill_disable=dict(type='bool'),
+ oom_score_adj=dict(type='int'),
+ pid=dict(type='str', aliases=['pid_mode']),
+ pids_limit=dict(type='str'),
+ pod=dict(type='str'),
+ privileged=dict(type='bool'),
+ publish=dict(type='list', elements='str', aliases=[
+ 'ports', 'published', 'published_ports']),
+ publish_all=dict(type='bool'),
+ read_only=dict(type='bool'),
+ read_only_tmpfs=dict(type='bool'),
+ recreate=dict(type='bool', default=False),
+ restart_policy=dict(type='str'),
+ rm=dict(type='bool', aliases=['remove', 'auto_remove']),
+ rootfs=dict(type='bool'),
+ security_opt=dict(type='list', elements='str'),
+ shm_size=dict(type='str'),
+ sig_proxy=dict(type='bool'),
+ stop_signal=dict(type='int'),
+ stop_timeout=dict(type='int'),
+ subgidname=dict(type='str'),
+ subuidname=dict(type='str'),
+ sysctl=dict(type='dict'),
+ systemd=dict(type='bool'),
+ tmpfs=dict(type='dict'),
+ tty=dict(type='bool'),
+ uidmap=dict(type='list', elements='str'),
+ ulimit=dict(type='list', aliases=['ulimits']),
+ user=dict(type='str'),
+ userns=dict(type='str', aliases=['userns_mode']),
+ uts=dict(type='str'),
+ volume=dict(type='list', elements='str', aliases=['volumes']),
+ volumes_from=dict(type='list', elements='str'),
+ workdir=dict(type='str', aliases=['working_dir'])
+)
+
+
+class PodmanModuleParams:
+ """Creates list of arguments for podman CLI command.
+
+ Arguments:
+ action {str} -- action type from 'run', 'stop', 'create', 'delete',
+ 'start'
+ params {dict} -- dictionary of module parameters
+
+ """
+
+ 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.
+
+ Returns:
+ list -- list of byte strings for Popen command
+ """
+ if self.action in ['start', 'stop', 'delete']:
+ return self.start_stop_delete()
+ 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")]
+ 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])
+ if func_name in all_param_methods:
+ cmd = getattr(self, func_name)(cmd)
+ cmd.append(self.params['image'])
+ if self.params['command']:
+ if isinstance(self.params['command'], list):
+ cmd += self.params['command']
+ else:
+ cmd += self.params['command'].split()
+ return [to_bytes(i, errors='surrogate_or_strict') for i in cmd]
+
+ def start_stop_delete(self):
+
+ if self.action in ['stop', 'start']:
+ cmd = [self.action, self.params['name']]
+ return [to_bytes(i, errors='surrogate_or_strict') for i in cmd]
+
+ if self.action == 'delete':
+ cmd = ['rm', '-f', self.params['name']]
+ return [to_bytes(i, errors='surrogate_or_strict') for i in cmd]
+
+ 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']]
+
+ def addparam_blkio_weight_device(self, c):
+ for blkio in self.params['blkio_weight_device'].items():
+ c += ['--blkio-weight-device', ':'.join(blkio)]
+ return c
+
+ def addparam_cap_add(self, c):
+ for cap_add in self.params['cap_add']:
+ c += ['--cap-add', cap_add]
+ return c
+
+ def addparam_cap_drop(self, c):
+ for cap_drop in self.params['cap_drop']:
+ 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']]
+
+ def addparam_cidfile(self, c):
+ return c + ['--cidfile', self.params['cidfile']]
+
+ def addparam_conmon_pidfile(self, c):
+ return c + ['--conmon-pidfile', self.params['conmon_pidfile']]
+
+ 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']]
+
+ def addparam_cpu_shares(self, c):
+ return c + ['--cpu-shares', self.params['cpu_shares']]
+
+ def addparam_cpus(self, c):
+ return c + ['--cpus', self.params['cpus']]
+
+ def addparam_cpuset_cpus(self, c):
+ return c + ['--cpuset-cpus', self.params['cpuset_cpus']]
+
+ 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):
+ for dev in self.params['device']:
+ c += ['--device', dev]
+ return c
+
+ def addparam_device_read_bps(self, c):
+ for dev in self.params['device_read_bps']:
+ c += ['--device-read-bps', dev]
+ return c
+
+ def addparam_device_read_iops(self, c):
+ for dev in self.params['device_read_iops']:
+ c += ['--device-read-iops', dev]
+ return c
+
+ def addparam_device_write_bps(self, c):
+ for dev in self.params['device_write_bps']:
+ c += ['--device-write-bps', dev]
+ return c
+
+ def addparam_device_write_iops(self, c):
+ 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'])]
+
+ def addparam_dns_option(self, c):
+ return c + ['--dns-option', self.params['dns_option']]
+
+ def addparam_dns_search(self, c):
+ return c + ['--dns-search', self.params['dns_search']]
+
+ def addparam_entrypoint(self, c):
+ return c + ['--entrypoint', self.params['entrypoint']]
+
+ def addparam_env(self, c):
+ for env_value in self.params['env'].items():
+ c += ['--env',
+ b"=".join([to_bytes(k, errors='surrogate_or_strict')
+ for k in env_value])]
+ return c
+
+ 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]
+ return c
+
+ def addparam_gidmap(self, c):
+ for gidmap in self.params['gidmap']:
+ c += ['--gidmap', gidmap]
+ return c
+
+ def addparam_group_add(self, c):
+ for g in self.params['group_add']:
+ c += ['--group-add', g]
+ return c
+
+ def addparam_healthcheck(self, c):
+ return c + ['--healthcheck-command', self.params['healthcheck']]
+
+ def addparam_healthcheck_interval(self, c):
+ return c + ['--healthcheck-interval',
+ self.params['healthcheck_interval']]
+
+ def addparam_healthcheck_retries(self, c):
+ return c + ['--healthcheck-retries',
+ self.params['healthcheck_retries']]
+
+ def addparam_healthcheck_start_period(self, c):
+ return c + ['--healthcheck-start-period',
+ self.params['healthcheck_start_period']]
+
+ def addparam_healthcheck_timeout(self, c):
+ return c + ['--healthcheck-timeout',
+ self.params['healthcheck_timeout']]
+
+ def addparam_hostname(self, c):
+ return c + ['--hostname', self.params['hostname']]
+
+ def addparam_http_proxy(self, c):
+ return c + ['--http-proxy=%s' % self.params['http_proxy']]
+
+ def addparam_image_volume(self, c):
+ return c + ['--image-volume', self.params['image_volume']]
+
+ def addparam_init(self, c):
+ if self.params['init']:
+ c += ['--init']
+ return c
+
+ def addparam_init_path(self, c):
+ return c + ['--init-path', self.params['init_path']]
+
+ def addparam_interactive(self, c):
+ return c + ['--interactive=%s' % self.params['interactive']]
+
+ def addparam_ip(self, c):
+ return c + ['--ip', self.params['ip']]
+
+ def addparam_ipc(self, c):
+ return c + ['--ipc', self.params['ipc']]
+
+ def addparam_kernel_memory(self, c):
+ return c + ['--kernel-memory', self.params['kernel_memory']]
+
+ def addparam_label(self, c):
+ for label in self.params['label'].items():
+ c += ['--label', b'='.join([to_bytes(l, errors='surrogate_or_strict')
+ for l in label])]
+ return c
+
+ def addparam_label_file(self, c):
+ return c + ['--label-file', self.params['label_file']]
+
+ def addparam_log_driver(self, c):
+ return c + ['--log-driver', self.params['log_driver']]
+
+ def addparam_log_opt(self, c):
+ for k, v in self.params['log_opt'].items():
+ if v is not None:
+ c += ['--log-opt',
+ b"=".join([to_bytes(k.replace('max_size', 'max-size'),
+ errors='surrogate_or_strict'),
+ to_bytes(v,
+ errors='surrogate_or_strict')])]
+ return c
+
+ def addparam_log_level(self, c):
+ return c + ['--log-level', self.params['log_level']]
+
+ def addparam_mac_address(self, c):
+ return c + ['--mac-address', self.params['mac_address']]
+
+ def addparam_memory(self, c):
+ return c + ['--memory', self.params['memory']]
+
+ def addparam_memory_reservation(self, c):
+ return c + ['--memory-reservation', self.params['memory_reservation']]
+
+ def addparam_memory_swap(self, c):
+ return c + ['--memory-swap', self.params['memory_swap']]
+
+ def addparam_memory_swappiness(self, c):
+ return c + ['--memory-swappiness', self.params['memory_swappiness']]
+
+ def addparam_mount(self, c):
+ return c + ['--mount', self.params['mount']]
+
+ def addparam_network(self, c):
+ return c + ['--network', ",".join(self.params['network'])]
+
+ def addparam_no_hosts(self, c):
+ return c + ['--no-hosts=%s' % self.params['no_hosts']]
+
+ def addparam_oom_kill_disable(self, c):
+ return c + ['--oom-kill-disable=%s' % self.params['oom_kill_disable']]
+
+ def addparam_oom_score_adj(self, c):
+ return c + ['--oom-score-adj', self.params['oom_score_adj']]
+
+ def addparam_pid(self, c):
+ return c + ['--pid', self.params['pid']]
+
+ def addparam_pids_limit(self, c):
+ return c + ['--pids-limit', self.params['pids_limit']]
+
+ def addparam_pod(self, c):
+ return c + ['--pod', self.params['pod']]
+
+ def addparam_privileged(self, c):
+ return c + ['--privileged=%s' % self.params['privileged']]
+
+ def addparam_publish(self, c):
+ for pub in self.params['publish']:
+ c += ['--publish', pub]
+ return c
+
+ def addparam_publish_all(self, c):
+ return c + ['--publish-all=%s' % self.params['publish_all']]
+
+ def addparam_read_only(self, c):
+ return c + ['--read-only=%s' % self.params['read_only']]
+
+ def addparam_read_only_tmpfs(self, c):
+ return c + ['--read-only-tmpfs=%s' % self.params['read_only_tmpfs']]
+
+ def addparam_restart_policy(self, c):
+ return c + ['--restart=%s' % self.params['restart_policy']]
+
+ def addparam_rm(self, c):
+ if self.params['rm']:
+ c += ['--rm']
+ return c
+
+ def addparam_rootfs(self, c):
+ return c + ['--rootfs=%s' % self.params['rootfs']]
+
+ def addparam_security_opt(self, c):
+ 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']]
+
+ def addparam_sig_proxy(self, c):
+ return c + ['--sig-proxy=%s' % self.params['sig_proxy']]
+
+ def addparam_stop_signal(self, c):
+ return c + ['--stop-signal', self.params['stop_signal']]
+
+ def addparam_stop_timeout(self, c):
+ return c + ['--stop-timeout', self.params['stop_timeout']]
+
+ def addparam_subgidname(self, c):
+ return c + ['--subgidname', self.params['subgidname']]
+
+ def addparam_subuidname(self, c):
+ return c + ['--subuidname', self.params['subuidname']]
+
+ def addparam_sysctl(self, c):
+ for sysctl in self.params['sysctl'].items():
+ c += ['--sysctl',
+ b"=".join([to_bytes(k, errors='surrogate_or_strict')
+ for k in sysctl])]
+ return c
+
+ def addparam_systemd(self, c):
+ return c + ['--systemd=%s' % self.params['systemd']]
+
+ def addparam_tmpfs(self, c):
+ for tmpfs in self.params['tmpfs'].items():
+ c += ['--tmpfs', ':'.join(tmpfs)]
+ return c
+
+ def addparam_tty(self, c):
+ return c + ['--tty=%s' % self.params['tty']]
+
+ def addparam_uidmap(self, c):
+ for uidmap in self.params['uidmap']:
+ c += ['--uidmap', uidmap]
+ return c
+
+ def addparam_ulimit(self, c):
+ for u in self.params['ulimit']:
+ c += ['--ulimit', u]
+ return c
+
+ def addparam_user(self, c):
+ return c + ['--user', self.params['user']]
+
+ def addparam_userns(self, c):
+ return c + ['--userns', self.params['userns']]
+
+ def addparam_uts(self, c):
+ return c + ['--uts', self.params['uts']]
+
+ def addparam_volume(self, c):
+ for vol in self.params['volume']:
+ if vol:
+ c += ['--volume', vol]
+ return c
+
+ def addparam_volumes_from(self, c):
+ for vol in self.params['volumes_from']:
+ c += ['--volumes-from', vol]
+ return c
+
+ def addparam_workdir(self, c):
+ return c + ['--workdir', self.params['workdir']]
+
+ # Add your own args for podman command
+ def addparam_cmd_args(self, c):
+ return c + self.params['cmd_args']
+
+
+class PodmanDefaults:
+ def __init__(self, image_info, podman_version):
+ self.version = podman_version
+ self.image_info = image_info
+ self.defaults = {
+ "blkio_weight": 0,
+ "cgroups": "default",
+ "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",
+ "log_level": "error",
+ "memory": "0",
+ "memory_swap": "0",
+ "memory_reservation": "0",
+ # "memory_swappiness": -1,
+ "no_hosts": False,
+ # libpod issue with networks in inspection
+ "oom_score_adj": 0,
+ "pid": "",
+ "privileged": False,
+ "rm": False,
+ "security_opt": [],
+ "stop_signal": self.image_info['config'].get('stopsignal', "15"),
+ "tty": False,
+ "user": self.image_info.get('user', ''),
+ "workdir": self.image_info['config'].get('workingdir', '/'),
+ "uts": "",
+ }
+
+ def default_dict(self):
+ # make here any changes to self.defaults related to podman version
+ # https://github.com/containers/libpod/pull/5669
+ if (LooseVersion(self.version) >= LooseVersion('1.8.0')
+ and LooseVersion(self.version) < LooseVersion('1.9.0')):
+ self.defaults['cpu_shares'] = 1024
+ if (LooseVersion(self.version) >= LooseVersion('2.0.0')):
+ self.defaults['network'] = ["slirp4netns"]
+ self.defaults['ipc'] = "private"
+ self.defaults['uts'] = "private"
+ self.defaults['pid'] = "private"
+ return self.defaults
+
+
+class PodmanContainerDiff:
+ def __init__(self, module, module_params, info, image_info, podman_version):
+ self.module = module
+ self.module_params = module_params
+ self.version = podman_version
+ self.default_dict = None
+ self.info = lower_keys(info)
+ self.image_info = lower_keys(image_info)
+ self.params = self.defaultize()
+ self.diff = {'before': {}, 'after': {}}
+ self.non_idempotent = {
+ 'env_file', # We can't get env vars from file to check
+ 'env_host',
+ }
+
+ def defaultize(self):
+ params_with_defaults = {}
+ self.default_dict = PodmanDefaults(
+ self.image_info, 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']
+ if after is None:
+ after = before
+ 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):
+ # TODO(sshnaidm): to inspect image to get the default command
+ if self.module_params['command'] is not None:
+ 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)
+ return False
+
+ 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]: "=".join(i.split("=")[1:])
+ for i in env_before}
+ after = before.copy()
+ if self.params['env']:
+ after.update({
+ k: v
+ 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)
+
+ # Healthcheck is only defined in container config if a healthcheck
+ # was configured; otherwise the config key isn't part of the config.
+ def diffparam_healthcheck(self):
+ if 'healthcheck' in self.info['config']:
+ # the "test" key is a list of 2 items where the first one is
+ # "CMD-SHELL" and the second one is the actual healthcheck command.
+ before = self.info['config']['healthcheck']['test'][1]
+ else:
+ before = ''
+ after = self.params['healthcheck'] or before
+ return self._diff_update_and_compare('healthcheck', 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_id = self.info['image']
+ after_id = self.image_info['id']
+ if before_id == after_id:
+ return self._diff_update_and_compare('image', before_id, after_id)
+ before = self.info['config']['image']
+ after = self.params['image']
+ mode = self.params['image_strict']
+ if mode is None or not mode:
+ # In a idempotency 'lite mode' assume all images from different registries are the same
+ before = before.replace(":latest", "")
+ after = after.replace(":latest", "")
+ before = before.split("/")[-1]
+ after = after.split("/")[-1]
+ else:
+ return self._diff_update_and_compare('image', before_id, after_id)
+ return self._diff_update_and_compare('image', before, after)
+
+ def diffparam_ipc(self):
+ before = self.info['hostconfig']['ipcmode']
+ after = self.params['ipc']
+ if self.params['pod'] and not self.module_params['ipc']:
+ after = before
+ return self._diff_update_and_compare('ipc', before, after)
+
+ def diffparam_label(self):
+ before = self.info['config']['labels'] or {}
+ after = self.image_info.get('labels') or {}
+ 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)
+
+ def diffparam_log_level(self):
+ excom = self.info['exitcommand']
+ if '--log-level' in excom:
+ before = excom[excom.index('--log-level') + 1].lower()
+ else:
+ before = self.params['log_level']
+ after = self.params['log_level']
+ return self._diff_update_and_compare('log_level', before, after)
+
+ # Parameter has limited idempotency, unable to guess the default log_path
+ def diffparam_log_opt(self):
+ before, after = {}, {}
+ # Log path
+ if 'logpath' in self.info:
+ path_before = self.info['logpath']
+ if (self.module_params['log_opt'] and
+ 'path' in self.module_params['log_opt'] and
+ self.module_params['log_opt']['path'] is not None):
+ path_after = self.params['log_opt']['path']
+ else:
+ path_after = path_before
+ if path_before != path_after:
+ before.update({'log-path': path_before})
+ after.update({'log-path': path_after})
+
+ # Log tag
+ if 'logtag' in self.info:
+ tag_before = self.info['logtag']
+ if (self.module_params['log_opt'] and
+ 'tag' in self.module_params['log_opt'] and
+ self.module_params['log_opt']['tag'] is not None):
+ tag_after = self.params['log_opt']['tag']
+ else:
+ tag_after = ''
+ if tag_before != tag_after:
+ before.update({'log-tag': tag_before})
+ after.update({'log-tag': tag_after})
+
+ return self._diff_update_and_compare('log_opt', before, after)
+
+ def diffparam_mac_address(self):
+ before = str(self.info['networksettings']['macaddress'])
+ if self.module_params['mac_address'] is not None:
+ after = self.params['mac_address']
+ else:
+ after = before
+ return self._diff_update_and_compare('mac_address', 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):
+ net_mode_before = self.info['hostconfig']['networkmode']
+ net_mode_after = ''
+ before = list(self.info['networksettings'].get('networks', {}))
+ after = self.params['network'] or []
+ # If container is in pod and no networks are provided
+ if not self.module_params['network'] and self.params['pod']:
+ after = before
+ return self._diff_update_and_compare('network', before, after)
+ # Check special network modes
+ if after in [['bridge'], ['host'], ['slirp4netns'], ['none']]:
+ net_mode_after = after[0]
+ # If changes are only for network mode and container has no networks
+ if net_mode_after and not before:
+ # Remove differences between v1 and v2
+ net_mode_after = net_mode_after.replace('bridge', 'default')
+ net_mode_after = net_mode_after.replace('slirp4netns', 'default')
+ net_mode_before = net_mode_before.replace('bridge', 'default')
+ net_mode_before = net_mode_before.replace('slirp4netns', 'default')
+ return self._diff_update_and_compare('network', net_mode_before, net_mode_after)
+ before, after = sorted(list(set(before))), sorted(list(set(after)))
+ return self._diff_update_and_compare('network', before, after)
+
+ def diffparam_no_hosts(self):
+ before = not bool(self.info['hostspath'])
+ # For newer verions of Podman
+ if 'resolvconfpath' in self.info:
+ before = not bool(self.info['resolvconfpath'])
+ 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)
+
+ # TODO(sshnaidm) Need to add port ranges support
+ def diffparam_publish(self):
+ ports = self.info['hostconfig']['portbindings']
+ before = [":".join([
+ j[0]['hostip'],
+ str(j[0]["hostport"]),
+ i.replace('/tcp', '')
+ ]).strip(':') for i, j in ports.items()]
+ after = self.params['publish'] or []
+ if self.params['publish_all']:
+ image_ports = self.image_info['config'].get('exposedports', {})
+ if image_ports:
+ after += list(image_ports.keys())
+ after = [i.replace("/tcp", "") for i in after]
+ # No support for port ranges yet
+ for ports in after:
+ if "-" in ports:
+ return self._diff_update_and_compare('publish', '', '')
+ before, after = sorted(list(set(before))), sorted(list(set(after)))
+ return self._diff_update_and_compare('publish', 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']
+ # In rootful containers with apparmor there is a default security opt
+ before = [o for o in before if 'apparmor=containers-default' not in o]
+ 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):
+ signals = {
+ "sighup": "1",
+ "sigint": "2",
+ "sigquit": "3",
+ "sigill": "4",
+ "sigtrap": "5",
+ "sigabrt": "6",
+ "sigiot": "6",
+ "sigbus": "7",
+ "sigfpe": "8",
+ "sigkill": "9",
+ "sigusr1": "10",
+ "sigsegv": "11",
+ "sigusr2": "12",
+ "sigpipe": "13",
+ "sigalrm": "14",
+ "sigterm": "15",
+ "sigstkflt": "16",
+ "sigchld": "17",
+ "sigcont": "18",
+ "sigstop": "19",
+ "sigtstp": "20",
+ "sigttin": "21",
+ "sigttou": "22",
+ "sigurg": "23",
+ "sigxcpu": "24",
+ "sigxfsz": "25",
+ "sigvtalrm": "26",
+ "sigprof": "27",
+ "sigwinch": "28",
+ "sigio": "29",
+ "sigpwr": "30",
+ "sigsys": "31"
+ }
+ before = str(self.info['config']['stopsignal'])
+ if not before.isdigit():
+ before = signals[before.lower()]
+ after = str(self.params['stop_signal'])
+ if not after.isdigit():
+ after = signals[after.lower()]
+ 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']
+ after = self.params['user']
+ return self._diff_update_and_compare('user', before, after)
+
+ def diffparam_ulimit(self):
+ after = self.params['ulimit'] or []
+ # In case of latest podman
+ if 'createcommand' in self.info['config']:
+ ulimits = []
+ for k, c in enumerate(self.info['config']['createcommand']):
+ if c == '--ulimit':
+ ulimits.append(self.info['config']['createcommand'][k + 1])
+ before = ulimits
+ before, after = sorted(before), sorted(after)
+ return self._diff_update_and_compare('ulimit', before, after)
+ if after:
+ ulimits = self.info['hostconfig']['ulimits']
+ before = {
+ u['name'].replace('rlimit_', ''): "%s:%s" % (u['soft'], u['hard']) for u in ulimits}
+ after = {i.split('=')[0]: i.split('=')[1]
+ for i in self.params['ulimit']}
+ new_before = []
+ new_after = []
+ for u in list(after.keys()):
+ # We don't support unlimited ulimits because it depends on platform
+ if u in before and "-1" not in after[u]:
+ new_before.append([u, before[u]])
+ new_after.append([u, after[u]])
+ return self._diff_update_and_compare('ulimit', new_before, new_after)
+ return self._diff_update_and_compare('ulimit', '', '')
+
+ def diffparam_uts(self):
+ before = self.info['hostconfig']['utsmode']
+ after = self.params['uts']
+ if self.params['pod'] and not self.module_params['uts']:
+ after = before
+ return self._diff_update_and_compare('uts', before, after)
+
+ def diffparam_volume(self):
+ def clean_volume(x):
+ '''Remove trailing and double slashes from volumes.'''
+ return x.replace("//", "/").rstrip("/")
+
+ before = self.info['mounts']
+ before_local_vols = []
+ if before:
+ volumes = []
+ local_vols = []
+ for m in before:
+ if m['type'] != 'volume':
+ volumes.append([m['source'], m['destination']])
+ elif m['type'] == 'volume':
+ local_vols.append([m['name'], m['destination']])
+ before = [":".join(v) for v in volumes]
+ before_local_vols = [":".join(v) for v in local_vols]
+ if self.params['volume'] is not None:
+ after = [":".join(
+ [clean_volume(i) for i in v.split(":")[:2]]
+ ) for v in self.params['volume']]
+ else:
+ after = []
+ if before_local_vols:
+ after = list(set(after).difference(before_local_vols))
+ before, after = sorted(list(set(before))), sorted(list(set(after)))
+ return self._diff_update_and_compare('volume', before, after)
+
+ def diffparam_volumes_from(self):
+ # Possibly volumesfrom is not in config
+ before = self.info['hostconfig'].get('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
+ 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, module_params):
+ """If image is passed, ensure it exists, if not - pull it or fail.
+
+ Arguments:
+ module {obj} -- ansible module object
+ image {str} -- name of image
+
+ Returns:
+ list -- list of image actions - if it pulled or nothing was done
+ """
+ image_actions = []
+ module_exec = module_params['executable']
+ if not image:
+ return image_actions
+ rc, out, err = module.run_command([module_exec, 'image', 'exists', image])
+ if rc == 0:
+ return image_actions
+ rc, out, err = module.run_command([module_exec, 'image', 'pull', image])
+ if rc != 0:
+ module.fail_json(msg="Can't pull image %s" % image, stdout=out,
+ stderr=err)
+ image_actions.append("pulled image %s" % image)
+ return image_actions
+
+
+class PodmanContainer:
+ """Perform container tasks.
+
+ Manages podman container, inspects it and checks its current state
+ """
+
+ def __init__(self, module, name, module_params):
+ """Initialize PodmanContainer class.
+
+ Arguments:
+ module {obj} -- ansible module object
+ name {str} -- name of container
+ """
+
+ self.module = module
+ self.module_params = module_params
+ 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):
+ """Check if container exists."""
+ return bool(self.info != {})
+
+ @property
+ def different(self):
+ """Check if container is different."""
+ diffcheck = PodmanContainerDiff(
+ self.module,
+ self.module_params,
+ self.info,
+ self.get_image_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):
+ """Return True if container is running now."""
+ return self.exists and self.info['State']['Running']
+
+ @property
+ def stopped(self):
+ """Return True if container exists and is not running now."""
+ return self.exists and not self.info['State']['Running']
+
+ def get_info(self):
+ """Inspect container and gather info about it."""
+ # pylint: disable=unused-variable
+ rc, out, err = self.module.run_command(
+ [self.module_params['executable'], b'container', b'inspect', self.name])
+ return json.loads(out)[0] if rc == 0 else {}
+
+ def get_image_info(self):
+ """Inspect container image and gather info about it."""
+ # pylint: disable=unused-variable
+ rc, out, err = self.module.run_command(
+ [self.module_params['executable'], b'image', b'inspect', self.module_params['image']])
+ return json.loads(out)[0] if rc == 0 else {}
+
+ def _get_podman_version(self):
+ # pylint: disable=unused-variable
+ 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.
+
+ Arguments:
+ action {str} -- action to perform - start, create, stop, run,
+ delete
+ """
+ b_command = PodmanModuleParams(action,
+ self.module_params,
+ self.version,
+ self.module,
+ ).construct_command_from_params()
+ if action == 'create':
+ b_command.remove(b'--detach=True')
+ full_cmd = " ".join([self.module_params['executable']]
+ + [to_native(i) for i in b_command])
+ self.actions.append(full_cmd)
+ if self.module.check_mode:
+ self.module.log(
+ "PODMAN-CONTAINER-DEBUG (check_mode): %s" % full_cmd)
+ else:
+ rc, out, err = self.module.run_command(
+ [self.module_params['executable'], b'container'] + b_command,
+ expand_user_and_vars=False)
+ self.module.log("PODMAN-CONTAINER-DEBUG: %s" % full_cmd)
+ if self.module_params['debug']:
+ self.module.log("PODMAN-CONTAINER-DEBUG STDOUT: %s" % out)
+ self.module.log("PODMAN-CONTAINER-DEBUG STDERR: %s" % err)
+ self.module.log("PODMAN-CONTAINER-DEBUG RC: %s" % rc)
+ 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."""
+ self._perform_action('run')
+
+ def delete(self):
+ """Delete the container."""
+ self._perform_action('delete')
+
+ def stop(self):
+ """Stop the container."""
+ self._perform_action('stop')
+
+ def start(self):
+ """Start the container."""
+ self._perform_action('start')
+
+ def create(self):
+ """Create the container."""
+ self._perform_action('create')
+
+ def recreate(self):
+ """Recreate the container."""
+ self.delete()
+ self.run()
+
+ def restart(self):
+ """Restart the container."""
+ self.stop()
+ self.start()
+
+
+class PodmanManager:
+ """Module manager class.
+
+ Defines according to parameters what actions should be applied to container
+ """
+
+ def __init__(self, module, params):
+ """Initialize PodmanManager class.
+
+ Arguments:
+ module {obj} -- ansible module object
+ """
+
+ self.module = module
+ self.results = {
+ 'changed': False,
+ 'actions': [],
+ 'container': {},
+ }
+ self.module_params = params
+ self.name = self.module_params['name']
+ self.executable = \
+ self.module.get_bin_path(self.module_params['executable'],
+ required=True)
+ self.image = self.module_params['image']
+ image_actions = ensure_image_exists(
+ self.module, self.image, self.module_params)
+ self.results['actions'] += image_actions
+ self.state = self.module_params['state']
+ self.restart = self.module_params['force_restart']
+ self.recreate = self.module_params['recreate']
+ self.container = PodmanContainer(
+ self.module, self.name, self.module_params)
+
+ def update_container_result(self, changed=True):
+ """Inspect the current container, update results with last info, exit.
+
+ Keyword Arguments:
+ changed {bool} -- whether any action was performed
+ (default: {True})
+ """
+ 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,
+ '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'] or self.module_params['debug']:
+ self.results.update({'podman_version': self.container.version})
+
+ def make_started(self):
+ """Run actions if desired state is 'started'."""
+ if self.container.running and \
+ (self.container.different or self.recreate):
+ self.container.recreate()
+ self.results['actions'].append('recreated %s' %
+ self.container.name)
+ self.update_container_result()
+ return
+ elif self.container.running and not self.container.different:
+ if self.restart:
+ self.container.restart()
+ self.results['actions'].append('restarted %s' %
+ self.container.name)
+ self.update_container_result()
+ return
+ self.update_container_result(changed=False)
+ return
+ elif not self.container.exists:
+ self.container.run()
+ self.results['actions'].append('started %s' % self.container.name)
+ self.update_container_result()
+ return
+ elif self.container.stopped and self.container.different:
+ self.container.recreate()
+ self.results['actions'].append('recreated %s' %
+ self.container.name)
+ self.update_container_result()
+ return
+ elif self.container.stopped and not self.container.different:
+ self.container.start()
+ self.results['actions'].append('started %s' % self.container.name)
+ self.update_container_result()
+ return
+
+ 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'
+ ' is not specified!')
+ if not self.container.exists:
+ self.container.create()
+ self.results['actions'].append('created %s' % self.container.name)
+ self.update_container_result()
+ return
+ if self.container.stopped:
+ self.update_container_result(changed=False)
+ return
+ elif self.container.running:
+ self.container.stop()
+ self.results['actions'].append('stopped %s' % self.container.name)
+ self.update_container_result()
+ return
+
+ def make_absent(self):
+ """Run actions if desired state is 'absent'."""
+ if not self.container.exists:
+ self.results.update({'changed': False})
+ elif self.container.exists:
+ self.container.delete()
+ self.results['actions'].append('deleted %s' % self.container.name)
+ self.results.update({'changed': True})
+ self.results.update({'container': {},
+ 'podman_actions': self.container.actions})
+
+ def execute(self):
+ """Execute the desired action according to map of actions & states."""
+ states_map = {
+ 'present': self.make_started,
+ 'started': self.make_started,
+ 'absent': self.make_absent,
+ 'stopped': self.make_stopped
+ }
+ process_action = states_map[self.state]
+ process_action()
+ return self.results
diff --git a/collections-debian-merged/ansible_collections/containers/podman/plugins/module_utils/podman/podman_pod_lib.py b/collections-debian-merged/ansible_collections/containers/podman/plugins/module_utils/podman/podman_pod_lib.py
new file mode 100644
index 00000000..c3f0e4e3
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/containers/podman/plugins/module_utils/podman/podman_pod_lib.py
@@ -0,0 +1,736 @@
+from __future__ import (absolute_import, division, print_function)
+import json
+from distutils.version import LooseVersion
+
+from ansible.module_utils._text import to_bytes, to_native
+
+from ansible_collections.containers.podman.plugins.module_utils.podman.common import lower_keys
+
+__metaclass__ = type
+
+ARGUMENTS_SPEC_POD = dict(
+ state=dict(
+ type='str',
+ default="created",
+ choices=[
+ 'created',
+ 'killed',
+ 'restarted',
+ 'absent',
+ 'started',
+ 'stopped',
+ 'paused',
+ 'unpaused',
+ ]),
+ recreate=dict(type='bool', default=False),
+ add_host=dict(type='list', required=False, elements='str'),
+ cgroup_parent=dict(type='str', required=False),
+ dns=dict(type='list', elements='str', required=False),
+ dns_opt=dict(type='list', elements='str', required=False),
+ dns_search=dict(type='list', elements='str', required=False),
+ hostname=dict(type='str', required=False),
+ infra=dict(type='bool', required=False),
+ infra_conmon_pidfile=dict(type='str', required=False),
+ infra_command=dict(type='str', required=False),
+ infra_image=dict(type='str', required=False),
+ ip=dict(type='str', required=False),
+ label=dict(type='dict', required=False),
+ label_file=dict(type='str', required=False),
+ mac_address=dict(type='str', required=False),
+ name=dict(type='str', required=True),
+ network=dict(type='str', required=False),
+ no_hosts=dict(type='bool', required=False),
+ pod_id_file=dict(type='str', required=False),
+ publish=dict(type='list', required=False,
+ elements='str', aliases=['ports']),
+ share=dict(type='str', required=False),
+ executable=dict(type='str', required=False, default='podman'),
+ debug=dict(type='bool', default=False),
+)
+
+
+class PodmanPodModuleParams:
+ """Creates list of arguments for podman CLI command.
+
+ Arguments:
+ action {str} -- action type from 'run', 'stop', 'create', 'delete',
+ 'start'
+ params {dict} -- dictionary of module parameters
+
+ """
+
+ 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.
+
+ Returns:
+ list -- list of byte strings for Popen command
+ """
+ if self.action in ['start', 'restart', 'stop', 'delete', 'pause',
+ 'unpause', 'kill']:
+ return self._simple_action()
+ if self.action in ['create']:
+ return self._create_action()
+ self.module.fail_json(msg="Unknown action %s" % self.action)
+
+ def _simple_action(self):
+ if self.action in ['start', 'restart', 'stop', 'pause', 'unpause', 'kill']:
+ cmd = [self.action, self.params['name']]
+ return [to_bytes(i, errors='surrogate_or_strict') for i in cmd]
+
+ if self.action == 'delete':
+ cmd = ['rm', '-f', self.params['name']]
+ return [to_bytes(i, errors='surrogate_or_strict') for i in cmd]
+ self.module.fail_json(msg="Unknown action %s" % self.action)
+
+ def _create_action(self):
+ cmd = [self.action]
+ all_param_methods = [func for func in dir(self)
+ 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])
+ if func_name in all_param_methods:
+ cmd = getattr(self, func_name)(cmd)
+ return [to_bytes(i, errors='surrogate_or_strict') for i in cmd]
+
+ 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_add_host(self, c):
+ for g in self.params['add_host']:
+ c += ['--add-host', g]
+ return c
+
+ def addparam_cgroup_parent(self, c):
+ return c + ['--cgroup-parent', self.params['cgroup_parent']]
+
+ def addparam_dns(self, c):
+ for g in self.params['dns']:
+ c += ['--dns', g]
+ return c
+
+ def addparam_dns_opt(self, c):
+ for g in self.params['dns_opt']:
+ c += ['--dns-opt', g]
+ return c
+
+ def addparam_dns_search(self, c):
+ for g in self.params['dns_search']:
+ c += ['--dns-search', g]
+ return c
+
+ def addparam_hostname(self, c):
+ return c + ['--hostname', self.params['hostname']]
+
+ def addparam_infra(self, c):
+ return c + [b'='.join([b'--infra',
+ to_bytes(self.params['infra'],
+ errors='surrogate_or_strict')])]
+
+ def addparam_infra_conmon_pidfile(self, c):
+ return c + ['--infra-conmon-pidfile', self.params['infra_conmon_pidfile']]
+
+ def addparam_infra_command(self, c):
+ return c + ['--infra-command', self.params['infra_command']]
+
+ def addparam_infra_image(self, c):
+ return c + ['--infra-image', self.params['infra_image']]
+
+ def addparam_ip(self, c):
+ return c + ['--ip', self.params['ip']]
+
+ def addparam_label(self, c):
+ for label in self.params['label'].items():
+ c += ['--label', b'='.join(
+ [to_bytes(l, errors='surrogate_or_strict') for l in label])]
+ return c
+
+ def addparam_label_file(self, c):
+ return c + ['--label-file', self.params['label_file']]
+
+ def addparam_mac_address(self, c):
+ return c + ['--mac-address', self.params['mac_address']]
+
+ def addparam_name(self, c):
+ return c + ['--name', self.params['name']]
+
+ def addparam_network(self, c):
+ return c + ['--network', self.params['network']]
+
+ def addparam_no_hosts(self, c):
+ return c + ["=".join('--no-hosts', self.params['no_hosts'])]
+
+ def addparam_pod_id_file(self, c):
+ return c + ['--pod-id-file', self.params['pod_id_file']]
+
+ def addparam_publish(self, c):
+ for g in self.params['publish']:
+ c += ['--publish', g]
+ return c
+
+ def addparam_share(self, c):
+ return c + ['--share', self.params['share']]
+
+
+class PodmanPodDefaults:
+ def __init__(self, module, podman_version):
+ self.module = module
+ self.version = podman_version
+ self.defaults = {
+ 'add_host': [],
+ 'dns': [],
+ 'dns_opt': [],
+ 'dns_search': [],
+ 'infra': True,
+ 'label': {},
+ }
+
+ def default_dict(self):
+ # make here any changes to self.defaults related to podman version
+ # https://github.com/containers/libpod/pull/5669
+ # if (LooseVersion(self.version) >= LooseVersion('1.8.0')
+ # and LooseVersion(self.version) < LooseVersion('1.9.0')):
+ # self.defaults['cpu_shares'] = 1024
+ return self.defaults
+
+
+class PodmanPodDiff:
+ def __init__(self, module, module_params, info, infra_info, podman_version):
+ self.module = module
+ self.module_params = module_params
+ self.version = podman_version
+ self.default_dict = None
+ self.info = lower_keys(info)
+ self.infra_info = lower_keys(infra_info)
+ self.params = self.defaultize()
+ self.diff = {'before': {}, 'after': {}}
+ self.non_idempotent = {}
+
+ def defaultize(self):
+ params_with_defaults = {}
+ self.default_dict = PodmanPodDefaults(
+ 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_add_host(self):
+ if not self.infra_info:
+ return self._diff_update_and_compare('add_host', '', '')
+ before = self.infra_info['hostconfig']['extrahosts'] or []
+ after = self.params['add_host']
+ before, after = sorted(list(set(before))), sorted(list(set(after)))
+ return self._diff_update_and_compare('add_host', before, after)
+
+ def diffparam_cgroup_parent(self):
+ if 'cgroupparent' in self.info:
+ before = self.info['cgroupparent']
+ elif 'config' in self.info and self.info['config'].get('cgroupparent'):
+ before = self.info['config']['cgroupparent']
+ after = self.params['cgroup_parent'] or before
+ return self._diff_update_and_compare('cgroup_parent', before, after)
+
+ def diffparam_dns(self):
+ if not self.infra_info:
+ return self._diff_update_and_compare('dns', '', '')
+ before = self.infra_info['hostconfig']['dns'] or []
+ after = self.params['dns']
+ before, after = sorted(list(set(before))), sorted(list(set(after)))
+ return self._diff_update_and_compare('dns', before, after)
+
+ def diffparam_dns_opt(self):
+ if not self.infra_info:
+ return self._diff_update_and_compare('dns_opt', '', '')
+ before = self.infra_info['hostconfig']['dnsoptions'] or []
+ after = self.params['dns_opt']
+ before, after = sorted(list(set(before))), sorted(list(set(after)))
+ return self._diff_update_and_compare('dns_opt', before, after)
+
+ def diffparam_dns_search(self):
+ if not self.infra_info:
+ return self._diff_update_and_compare('dns_search', '', '')
+ before = self.infra_info['hostconfig']['dnssearch'] or []
+ after = self.params['dns_search']
+ before, after = sorted(list(set(before))), sorted(list(set(after)))
+ return self._diff_update_and_compare('dns_search', before, after)
+
+ def diffparam_hostname(self):
+ if not self.infra_info:
+ return self._diff_update_and_compare('hostname', '', '')
+ before = self.infra_info['config']['hostname']
+ after = self.params['hostname'] or before
+ return self._diff_update_and_compare('hostname', before, after)
+
+ # TODO(sshnaidm): https://github.com/containers/podman/issues/6968
+ def diffparam_infra(self):
+ if 'state' in self.info and 'infracontainerid' in self.info['state']:
+ before = self.info['state']['infracontainerid'] != ""
+ else:
+ # TODO(sshnaidm): https://github.com/containers/podman/issues/6968
+ before = 'infracontainerid' in self.info
+ after = self.params['infra']
+ return self._diff_update_and_compare('infra', before, after)
+
+ # TODO(sshnaidm): https://github.com/containers/podman/issues/6969
+ # def diffparam_infra_command(self):
+ # before = str(self.info['hostconfig']['infra_command'])
+ # after = self.params['infra_command']
+ # return self._diff_update_and_compare('infra_command', before, after)
+
+ def diffparam_infra_image(self):
+ if not self.infra_info:
+ return self._diff_update_and_compare('infra_image', '', '')
+ before = str(self.infra_info['imagename'])
+ after = before
+ if self.module_params['infra_image']:
+ after = self.params['infra_image']
+ before = before.replace(":latest", "")
+ after = after.replace(":latest", "")
+ before = before.split("/")[-1]
+ after = after.split("/")[-1]
+ return self._diff_update_and_compare('infra_image', before, after)
+
+ # TODO(sshnaidm): https://github.com/containers/podman/pull/6956
+ # def diffparam_ip(self):
+ # before = str(self.info['hostconfig']['ip'])
+ # after = self.params['ip']
+ # return self._diff_update_and_compare('ip', before, after)
+
+ def diffparam_label(self):
+ if 'config' in self.info and 'labels' in self.info['config']:
+ before = self.info['config'].get('labels') or {}
+ else:
+ before = self.info['labels'] if 'labels' in self.info else {}
+ after = self.params['label']
+ return self._diff_update_and_compare('label', before, after)
+
+ # TODO(sshnaidm): https://github.com/containers/podman/pull/6956
+ # def diffparam_mac_address(self):
+ # before = str(self.info['hostconfig']['mac_address'])
+ # after = self.params['mac_address']
+ # return self._diff_update_and_compare('mac_address', before, after)
+
+ def diffparam_network(self):
+ if not self.infra_info:
+ return self._diff_update_and_compare('network', [], [])
+ net_mode_before = self.infra_info['hostconfig']['networkmode']
+ net_mode_after = ''
+ before = self.infra_info['networksettings'].get('networks', [])
+ after = self.params['network']
+ # Currently supported only 'host' and 'none' network modes idempotency
+ if after in ['bridge', 'host', 'slirp4netns']:
+ net_mode_after = after
+ elif after:
+ after = after.split(",")
+ else:
+ after = []
+ if net_mode_after and not before:
+ # Remove differences between v1 and v2
+ net_mode_after = net_mode_after.replace('bridge', 'default')
+ net_mode_after = net_mode_after.replace('slirp4netns', 'default')
+ net_mode_before = net_mode_before.replace('bridge', 'default')
+ net_mode_before = net_mode_before.replace('slirp4netns', 'default')
+ return self._diff_update_and_compare('network', net_mode_before, net_mode_after)
+ before, after = sorted(list(set(before))), sorted(list(set(after)))
+ return self._diff_update_and_compare('network', before, after)
+
+ # TODO(sshnaidm)
+ # def diffparam_no_hosts(self):
+ # before = str(self.info['hostconfig']['no_hosts'])
+ # after = self.params['no_hosts']
+ # return self._diff_update_and_compare('no_hosts', before, after)
+
+ # TODO(sshnaidm) Need to add port ranges support
+ def diffparam_publish(self):
+ if not self.infra_info:
+ return self._diff_update_and_compare('publish', '', '')
+ ports = self.infra_info['hostconfig']['portbindings']
+ before = [":".join([
+ j[0]['hostip'],
+ str(j[0]["hostport"]),
+ i.replace('/tcp', '')
+ ]).strip(':') for i, j in ports.items()]
+ after = self.params['publish'] or []
+ after = [i.replace("/tcp", "") for i in after]
+ # No support for port ranges yet
+ for ports in after:
+ if "-" in ports:
+ return self._diff_update_and_compare('publish', '', '')
+ before, after = sorted(list(set(before))), sorted(list(set(after)))
+ return self._diff_update_and_compare('publish', before, after)
+
+ def diffparam_share(self):
+ if not self.infra_info:
+ return self._diff_update_and_compare('share', '', '')
+ if 'sharednamespaces' in self.info:
+ before = self.info['sharednamespaces']
+ elif 'config' in self.info:
+ before = [
+ i.split('shares')[1].lower()
+ for i in self.info['config'] if 'shares' in i]
+ # TODO(sshnaidm): to discover why in podman v1 'cgroup' appears
+ before.remove('cgroup')
+ else:
+ before = []
+ if self.params['share'] is not None:
+ after = self.params['share'].split(",")
+ else:
+ after = ['uts', 'ipc', 'net']
+ before, after = sorted(list(set(before))), sorted(list(set(after)))
+ return self._diff_update_and_compare('share', 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
+ 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
+
+
+class PodmanPod:
+ """Perform pod tasks.
+
+ Manages podman pod, inspects it and checks its current state
+ """
+
+ def __init__(self, module, name, module_params):
+ """Initialize PodmanPod class.
+
+ Arguments:
+ module {obj} -- ansible module object
+ name {str} -- name of pod
+ """
+
+ self.module = module
+ self.module_params = module_params
+ self.name = name
+ self.stdout, self.stderr = '', ''
+ self.info = self.get_info()
+ self.infra_info = self.get_infra_info()
+ self.version = self._get_podman_version()
+ self.diff = {}
+ self.actions = []
+
+ @property
+ def exists(self):
+ """Check if pod exists."""
+ return bool(self.info != {})
+
+ @property
+ def different(self):
+ """Check if pod is different."""
+ diffcheck = PodmanPodDiff(
+ self.module,
+ self.module_params,
+ self.info,
+ self.infra_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):
+ """Return True if pod is running now."""
+ if 'status' in self.info['State']:
+ return self.info['State']['status'] == 'Running'
+ return self.info['State'] == 'Running'
+
+ @property
+ def paused(self):
+ """Return True if pod is paused now."""
+ if 'status' in self.info['State']:
+ return self.info['State']['status'] == 'Paused'
+ return self.info['State'] == 'Paused'
+
+ @property
+ def stopped(self):
+ """Return True if pod exists and is not running now."""
+ if not self.exists:
+ return False
+ if 'status' in self.info['State']:
+ return not (self.info['State']['status'] == 'Running')
+ return not (self.info['State'] == 'Running')
+
+ def get_info(self):
+ """Inspect pod and gather info about it."""
+ # pylint: disable=unused-variable
+ rc, out, err = self.module.run_command(
+ [self.module_params['executable'], b'pod', b'inspect', self.name])
+ return json.loads(out) if rc == 0 else {}
+
+ def get_infra_info(self):
+ """Inspect pod and gather info about it."""
+ if not self.info:
+ return {}
+ if 'InfraContainerID' in self.info:
+ infra_container_id = self.info['InfraContainerID']
+ elif 'State' in self.info and 'infraContainerID' in self.info['State']:
+ infra_container_id = self.info['State']['infraContainerID']
+ else:
+ return {}
+ # pylint: disable=unused-variable
+ rc, out, err = self.module.run_command(
+ [self.module_params['executable'], b'inspect', infra_container_id])
+ return json.loads(out)[0] if rc == 0 else {}
+
+ def _get_podman_version(self):
+ # pylint: disable=unused-variable
+ 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 pod.
+
+ Arguments:
+ action {str} -- action to perform - start, create, stop, pause
+ unpause, delete, restart, kill
+ """
+ b_command = PodmanPodModuleParams(action,
+ self.module_params,
+ self.version,
+ self.module,
+ ).construct_command_from_params()
+ full_cmd = " ".join([self.module_params['executable'], 'pod']
+ + [to_native(i) for i in b_command])
+ self.module.log("PODMAN-POD-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'pod'] + b_command,
+ expand_user_and_vars=False)
+ self.stdout = out
+ self.stderr = err
+ if rc != 0:
+ self.module.fail_json(
+ msg="Can't %s pod %s" % (action, self.name),
+ stdout=out, stderr=err)
+
+ def delete(self):
+ """Delete the pod."""
+ self._perform_action('delete')
+
+ def stop(self):
+ """Stop the pod."""
+ self._perform_action('stop')
+
+ def start(self):
+ """Start the pod."""
+ self._perform_action('start')
+
+ def create(self):
+ """Create the pod."""
+ self._perform_action('create')
+
+ def recreate(self):
+ """Recreate the pod."""
+ self.delete()
+ self.create()
+
+ def restart(self):
+ """Restart the pod."""
+ self._perform_action('restart')
+
+ def kill(self):
+ """Kill the pod."""
+ self._perform_action('kill')
+
+ def pause(self):
+ """Pause the pod."""
+ self._perform_action('pause')
+
+ def unpause(self):
+ """Unpause the pod."""
+ self._perform_action('unpause')
+
+
+class PodmanPodManager:
+ """Module manager class.
+
+ Defines according to parameters what actions should be applied to pod
+ """
+
+ def __init__(self, module, params):
+ """Initialize PodmanManager class.
+
+ Arguments:
+ module {obj} -- ansible module object
+ """
+
+ self.module = module
+ self.module_params = params
+ self.results = {
+ 'changed': False,
+ 'actions': [],
+ 'pod': {},
+ }
+ self.name = self.module_params['name']
+ self.executable = \
+ self.module.get_bin_path(self.module_params['executable'],
+ required=True)
+ self.state = self.module_params['state']
+ self.recreate = self.module_params['recreate']
+ self.pod = PodmanPod(self.module, self.name, self.module_params)
+
+ def update_pod_result(self, changed=True):
+ """Inspect the current pod, update results with last info, exit.
+
+ Keyword Arguments:
+ changed {bool} -- whether any action was performed
+ (default: {True})
+ """
+ facts = self.pod.get_info() if changed else self.pod.info
+ out, err = self.pod.stdout, self.pod.stderr
+ self.results.update({'changed': changed, 'pod': facts,
+ 'podman_actions': self.pod.actions},
+ stdout=out, stderr=err)
+ if self.pod.diff:
+ self.results.update({'diff': self.pod.diff})
+ if self.module.params['debug'] or self.module_params['debug']:
+ self.results.update({'podman_version': self.pod.version})
+
+ def execute(self):
+ """Execute the desired action according to map of actions & states."""
+ states_map = {
+ 'created': self.make_created,
+ 'started': self.make_started,
+ 'stopped': self.make_stopped,
+ 'absent': self.make_absent,
+ 'killed': self.make_killed,
+ 'paused': self.make_paused,
+ 'unpaused': self.make_unpaused,
+
+ }
+ process_action = states_map[self.state]
+ process_action()
+ return self.results
+
+ def _create_or_recreate_pod(self):
+ """Ensure pod exists and is exactly as it should be by input params."""
+ changed = False
+ if self.pod.exists:
+ if self.pod.different or self.recreate:
+ self.pod.recreate()
+ self.results['actions'].append('recreated %s' % self.pod.name)
+ changed = True
+ elif not self.pod.exists:
+ self.pod.create()
+ self.results['actions'].append('created %s' % self.pod.name)
+ changed = True
+ return changed
+
+ def make_created(self):
+ """Run actions if desired state is 'created'."""
+ if self.pod.exists and not self.pod.different:
+ self.update_pod_result(changed=False)
+ return
+ self._create_or_recreate_pod()
+ self.update_pod_result()
+
+ def make_killed(self):
+ """Run actions if desired state is 'killed'."""
+ self._create_or_recreate_pod()
+ self.pod.kill()
+ self.results['actions'].append('killed %s' % self.pod.name)
+ self.update_pod_result()
+
+ def make_paused(self):
+ """Run actions if desired state is 'paused'."""
+ changed = self._create_or_recreate_pod()
+ if self.pod.paused:
+ self.update_pod_result(changed=changed)
+ return
+ self.pod.pause()
+ self.results['actions'].append('paused %s' % self.pod.name)
+ self.update_pod_result()
+
+ def make_unpaused(self):
+ """Run actions if desired state is 'unpaused'."""
+ changed = self._create_or_recreate_pod()
+ if not self.pod.paused:
+ self.update_pod_result(changed=changed)
+ return
+ self.pod.unpause()
+ self.results['actions'].append('unpaused %s' % self.pod.name)
+ self.update_pod_result()
+
+ def make_started(self):
+ """Run actions if desired state is 'started'."""
+ changed = self._create_or_recreate_pod()
+ if not changed and self.pod.running:
+ self.update_pod_result(changed=changed)
+ return
+
+ # self.pod.unpause() TODO(sshnaidm): to unpause if state == started?
+ self.pod.start()
+ self.results['actions'].append('started %s' % self.pod.name)
+ self.update_pod_result()
+
+ def make_stopped(self):
+ """Run actions if desired state is 'stopped'."""
+ changed = self._create_or_recreate_pod()
+ if changed or self.pod.stopped:
+ self.update_pod_result(changed=changed)
+ return
+ elif self.pod.running:
+ self.pod.stop()
+ self.results['actions'].append('stopped %s' % self.pod.name)
+ self.update_pod_result()
+
+ def make_absent(self):
+ """Run actions if desired state is 'absent'."""
+ if not self.pod.exists:
+ self.results.update({'changed': False})
+ elif self.pod.exists:
+ self.pod.delete()
+ self.results['actions'].append('deleted %s' % self.pod.name)
+ self.results.update({'changed': True})
+ self.results.update({'pod': {},
+ 'podman_actions': self.pod.actions})
diff --git a/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/__init__.py b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/__init__.py
diff --git a/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_container.py b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_container.py
new file mode 100644
index 00000000..99b14a39
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_container.py
@@ -0,0 +1,916 @@
+#!/usr/bin/python
+# Copyright (c) 2020 Red Hat
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+# flake8: noqa: E501
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+
+DOCUMENTATION = r"""
+module: podman_container
+author:
+ - "Sagi Shnaidman (@sshnaidm)"
+version_added: '1.0.0'
+short_description: Manage podman containers
+notes: []
+description:
+ - Start, stop, restart and manage Podman containers
+requirements:
+ - podman
+options:
+ name:
+ description:
+ - Name of the container
+ required: True
+ type: 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
+ state:
+ description:
+ - I(absent) - A container matching the specified name will be stopped and
+ removed.
+ - I(present) - Asserts the existence of a container matching the name and
+ any provided configuration parameters. If no container matches the
+ name, a container will be created. If a container matches the name but
+ the provided configuration does not match, the container will be
+ updated, if it can be. If it cannot be updated, it will be removed and
+ re-created with the requested config. Image version will be taken into
+ account when comparing configuration. Use the recreate option to force
+ the re-creation of the matching container.
+ - I(started) - Asserts there is a running container matching the name and
+ any provided configuration. If no container matches the name, a
+ container will be created and started. Use recreate to always re-create
+ a matching container, even if it is running. Use force_restart to force
+ a matching container to be stopped and restarted.
+ - I(stopped) - Asserts that the container is first I(present), and then
+ if the container is running moves it to a stopped state.
+ type: str
+ default: started
+ choices:
+ - absent
+ - present
+ - stopped
+ - started
+ image:
+ description:
+ - Repository path (or image name) and tag used to create the container.
+ If an image is not found, the image will be pulled from the registry.
+ If no tag is included, C(latest) will be used.
+ - Can also be an image ID. If this is the case, the image is assumed to
+ be available locally.
+ type: str
+ annotation:
+ description:
+ - Add an annotation to the container. The format is key value, multiple
+ times.
+ type: dict
+ authfile:
+ description:
+ - Path of the authentication file. Default is
+ ``${XDG_RUNTIME_DIR}/containers/auth.json``
+ (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: path
+ blkio_weight:
+ description:
+ - Block IO weight (relative weight) accepts a weight value between 10 and
+ 1000
+ type: int
+ blkio_weight_device:
+ description:
+ - Block IO weight (relative device weight, format DEVICE_NAME[:]WEIGHT).
+ type: dict
+ cap_add:
+ description:
+ - List of capabilities to add to the container.
+ type: list
+ elements: str
+ aliases:
+ - capabilities
+ cap_drop:
+ description:
+ - List of capabilities to drop from the container.
+ type: list
+ elements: str
+ cgroup_parent:
+ description:
+ - Path to cgroups under which the cgroup for the container will be
+ created.
+ 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
+ type: path
+ cmd_args:
+ description:
+ - Any additional command options you want to pass to podman command,
+ cmd_args - ['--other-param', 'value']
+ Be aware module doesn't support idempotency if this is set.
+ type: list
+ elements: str
+ conmon_pidfile:
+ description:
+ - Write the pid of the conmon process to a file.
+ conmon runs in a separate process than Podman,
+ so this is necessary when using systemd to restart Podman containers.
+ type: path
+ command:
+ description:
+ - Override command of container. Can be a string or a list.
+ type: raw
+ cpu_period:
+ 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.
+ 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:
+ - CPU shares (relative weight)
+ type: int
+ cpus:
+ description:
+ - Number of CPUs. The default is 0.0 which means no limit.
+ type: str
+ cpuset_cpus:
+ description:
+ - CPUs in which to allow execution (0-3, 0,1)
+ type: str
+ cpuset_mems:
+ description:
+ - Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only
+ effective on NUMA systems.
+ type: str
+ detach:
+ description:
+ - 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
+ character or ctrl-value
+ type: str
+ device:
+ description:
+ - 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: 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: list
+ device_read_iops:
+ description:
+ - Limit read rate (IO per second) from a device
+ (e.g. device-read-iops /dev/sda:1000)
+ type: list
+ device_write_bps:
+ description:
+ - Limit write rate (bytes per second) to a device
+ (e.g. device-write-bps /dev/sda:1mb)
+ type: list
+ device_write_iops:
+ description:
+ - Limit write rate (IO per second) to a device
+ (e.g. device-write-iops /dev/sda:1000)
+ type: list
+ dns:
+ description:
+ - Set custom DNS servers
+ type: list
+ elements: str
+ aliases:
+ - dns_servers
+ dns_option:
+ description:
+ - Set custom DNS options
+ type: str
+ aliases:
+ - dns_opts
+ dns_search:
+ description:
+ - Set custom DNS search domains (Use dns_search with '' if you don't wish
+ to set the search domain)
+ type: str
+ aliases:
+ - dns_search_domains
+ entrypoint:
+ description:
+ - Overwrite the default ENTRYPOINT of the image
+ type: str
+ env:
+ description:
+ - Set environment variables.
+ This option allows you to specify arbitrary environment variables that
+ are available for the process that will be launched inside of the
+ container.
+ type: dict
+ env_file:
+ 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
+ dictionary. Each host name will be added to the container's
+ ``/etc/hosts`` file.
+ type: dict
+ aliases:
+ - add_hosts
+ expose:
+ description:
+ - Expose a port, or a range of ports (e.g. expose "3300-3310") to set up
+ port redirection on the host system.
+ type: list
+ elements: str
+ aliases:
+ - exposed
+ - exposed_ports
+ force_restart:
+ description:
+ - Force restart of container.
+ type: bool
+ default: False
+ aliases:
+ - restart
+ gidmap:
+ description:
+ - Run the container in a new user namespace using the supplied mapping.
+ type: list
+ elements: str
+ group_add:
+ description:
+ - Add additional groups to run as
+ type: list
+ aliases:
+ - groups
+ healthcheck:
+ description:
+ - Set or alter a healthcheck command for a container.
+ type: str
+ healthcheck_interval:
+ description:
+ - Set an interval for the healthchecks
+ (a value of disable results in no automatic timer setup)
+ (default "30s")
+ type: str
+ healthcheck_retries:
+ description:
+ - The number of retries allowed before a healthcheck is considered to be
+ unhealthy. The default value is 3.
+ type: int
+ healthcheck_start_period:
+ description:
+ - The initialization time needed for a container to bootstrap.
+ The value can be expressed in time format like 2m3s. The default value
+ is 0s
+ type: str
+ healthcheck_timeout:
+ description:
+ - The maximum time allowed to complete the healthcheck before an interval
+ is considered failed. Like start-period, the value can be expressed in
+ a time format such as 1m22s. The default value is 30s
+ type: str
+ hostname:
+ description:
+ - Container host name. Sets the container host name that is available
+ inside the container.
+ type: str
+ http_proxy:
+ description:
+ - By default proxy environment variables are passed into the container if
+ set for the podman process. This can be disabled by setting the
+ http_proxy option to false. The environment variables passed in
+ include http_proxy, https_proxy, ftp_proxy, no_proxy, and also the
+ upper case versions of those.
+ Defaults to true
+ type: bool
+ image_volume:
+ description:
+ - Tells podman how to handle the builtin image volumes.
+ The options are bind, tmpfs, or ignore (default bind)
+ type: str
+ choices:
+ - 'bind'
+ - 'tmpfs'
+ - 'ignore'
+ image_strict:
+ description:
+ - Whether to compare images in idempotency by taking into account a full
+ name with registry and namespaces.
+ type: bool
+ default: False
+ init:
+ description:
+ - Run an init inside the container that forwards signals and reaps
+ processes. The default is false.
+ type: bool
+ init_path:
+ description:
+ - Path to the container-init binary.
+ type: str
+ interactive:
+ description:
+ - Keep STDIN open even if not attached. The default is false.
+ When set to true, keep stdin open even if not attached.
+ The default is false.
+ type: bool
+ ip:
+ description:
+ - Specify a static IP address for the container, for example
+ '10.88.64.128'.
+ Can only be used if no additional CNI networks to join were specified
+ via 'network:', and if the container is not joining another container's
+ network namespace via 'network container:<name|id>'.
+ The address must be within the default CNI network's pool
+ (default 10.88.0.0/16).
+ type: str
+ ipc:
+ description:
+ - Default is to create a private IPC namespace (POSIX SysV IPC) for the
+ container
+ type: str
+ aliases:
+ - ipc_mode
+ kernel_memory:
+ 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:
+ - Add metadata to a container, pass dictionary of label names and values
+ aliases:
+ - labels
+ type: dict
+ label_file:
+ description:
+ - Read in a line delimited file of labels
+ type: str
+ log_driver:
+ description:
+ - Logging driver. Used to set the log driver for the container.
+ For example log_driver "k8s-file".
+ type: str
+ choices:
+ - k8s-file
+ - journald
+ - json-file
+ log_level:
+ description:
+ - Logging level for Podman. Log messages above specified level
+ ("debug"|"info"|"warn"|"error"|"fatal"|"panic") (default "error")
+ type: str
+ choices:
+ - debug
+ - info
+ - warn
+ - error
+ - fatal
+ - panic
+ log_opt:
+ description:
+ - Logging driver specific options. Used to set the path to the container
+ log file.
+ type: dict
+ aliases:
+ - log_options
+ suboptions:
+ path:
+ description:
+ - Specify a path to the log file (e.g. /var/log/container/mycontainer.json).
+ type: str
+ required: false
+ max_size:
+ description:
+ - Specify a max size of the log file (e.g 10mb).
+ type: str
+ required: false
+ tag:
+ description:
+ - Specify a custom log tag for the container.
+ type: str
+ required: false
+
+ mac_address:
+ description:
+ - Specify a MAC address for the container, for example
+ '92:d0:c6:0a:29:33'.
+ Don't forget that it must be unique within one Ethernet network.
+ type: str
+ 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:
+ - A limit value equal to memory plus swap. Must be used with the -m
+ (--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:
+ - Tune a container's memory swappiness behavior. Accepts an integer
+ between 0 and 100.
+ type: int
+ mount:
+ description:
+ - Attach a filesystem mount to the container. bind or tmpfs
+ For example mount
+ "type=bind,source=/path/on/host,destination=/path/in/container"
+ type: str
+ network:
+ description:
+ - Set the Network mode for the container
+ * bridge create a network stack on the default bridge
+ * none no networking
+ * container:<name|id> reuse another container's network stack
+ * host use the podman host network stack.
+ * <network-name>|<network-id> connect to a user-defined network
+ * 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: list
+ elements: str
+ aliases:
+ - net
+ - network_mode
+ no_hosts:
+ description:
+ - Do not create /etc/hosts for the container
+ Default is false.
+ type: bool
+ oom_kill_disable:
+ description:
+ - Whether to disable OOM Killer for the container or not.
+ Default is false.
+ type: bool
+ oom_score_adj:
+ description:
+ - Tune the host's OOM preferences for containers (accepts -1000 to 1000)
+ type: int
+ pid:
+ description:
+ - Set the PID mode for the container
+ type: str
+ aliases:
+ - pid_mode
+ pids_limit:
+ description:
+ - Tune the container's PIDs limit. Set -1 to have unlimited PIDs for the
+ container.
+ type: str
+ pod:
+ description:
+ - Run container in an existing pod.
+ If you want podman to make the pod for you, preference the pod name
+ with "new:"
+ type: str
+ privileged:
+ description:
+ - Give extended privileges to this container. The default is false.
+ type: bool
+ publish:
+ description:
+ - Publish a container's port, or range of ports, to the host.
+ Format - ip:hostPort:containerPort | ip::containerPort |
+ hostPort:containerPort | containerPort
+ In case of only containerPort is set, the hostPort will chosen
+ randomly by Podman.
+ type: list
+ elements: str
+ aliases:
+ - ports
+ - published
+ - published_ports
+ publish_all:
+ description:
+ - Publish all exposed ports to random ports on the host interfaces. The
+ default is false.
+ type: bool
+ read_only:
+ description:
+ - Mount the container's root filesystem as read only. Default is false
+ type: bool
+ read_only_tmpfs:
+ description:
+ - If container is running in --read-only mode, then mount a read-write
+ tmpfs on /run, /tmp, and /var/tmp. The default is true
+ type: bool
+ recreate:
+ description:
+ - Use with present and started states to force the re-creation of an
+ existing container.
+ type: bool
+ default: False
+ restart_policy:
+ description:
+ - Restart policy to follow when containers exit.
+ Restart policy will not take effect if a container is stopped via the
+ podman kill or podman stop commands. Valid values are
+ * no - Do not restart containers on exit
+ * on-failure[:max_retries] - Restart containers when they exit with a
+ non-0 exit code, retrying indefinitely
+ or until the optional max_retries count is hit
+ * always - Restart containers when they exit, regardless of status,
+ retrying indefinitely
+ type: str
+ rm:
+ description:
+ - Automatically remove the container when it exits. The default is false.
+ type: bool
+ aliases:
+ - remove
+ - auto_remove
+ rootfs:
+ description:
+ - If true, the first argument refers to an exploded container on the file
+ system. The default is false.
+ type: bool
+ security_opt:
+ description:
+ - Security Options. For example security_opt "seccomp=unconfined"
+ type: list
+ elements: str
+ shm_size:
+ description:
+ - Size of /dev/shm. The format is <number><unit>. number must be greater
+ than 0.
+ Unit is optional and can be b (bytes), k (kilobytes), m(megabytes), or
+ g (gigabytes).
+ If you omit the unit, the system uses bytes. If you omit the size
+ entirely, the system uses 64m
+ type: str
+ sig_proxy:
+ description:
+ - Proxy signals sent to the podman run command to the container process.
+ SIGCHLD, SIGSTOP, and SIGKILL are not proxied. The default is true.
+ type: bool
+ stop_signal:
+ description:
+ - Signal to stop a container. Default is SIGTERM.
+ type: int
+ stop_timeout:
+ description:
+ - Timeout (in seconds) to stop a container. Default is 10.
+ type: int
+ subgidname:
+ description:
+ - Run the container in a new user namespace using the map with 'name' in
+ the /etc/subgid file.
+ type: str
+ subuidname:
+ description:
+ - Run the container in a new user namespace using the map with 'name' in
+ the /etc/subuid file.
+ type: str
+ sysctl:
+ description:
+ - Configure namespaced kernel parameters at runtime
+ type: dict
+ systemd:
+ description:
+ - Run container in systemd mode. The default is true.
+ type: bool
+ tmpfs:
+ description:
+ - Create a tmpfs mount. For example tmpfs
+ "/tmp" "rw,size=787448k,mode=1777"
+ type: dict
+ tty:
+ description:
+ - Allocate a pseudo-TTY. The default is false.
+ type: bool
+ uidmap:
+ description:
+ - Run the container in a new user namespace using the supplied mapping.
+ type: list
+ elements: str
+ ulimit:
+ description:
+ - Ulimit options
+ type: list
+ aliases:
+ - ulimits
+ user:
+ description:
+ - Sets the username or UID used and optionally the groupname or GID for
+ the specified command.
+ type: str
+ userns:
+ description:
+ - Set the user namespace mode for the container.
+ It defaults to the PODMAN_USERNS environment variable.
+ An empty value means user namespaces are disabled.
+ type: str
+ aliases:
+ - userns_mode
+ uts:
+ description:
+ - Set the UTS mode for the container
+ type: str
+ volume:
+ description:
+ - Create a bind mount. If you specify, volume /HOST-DIR:/CONTAINER-DIR,
+ podman bind mounts /HOST-DIR in the host to /CONTAINER-DIR in the
+ podman container.
+ type: list
+ elements: str
+ aliases:
+ - volumes
+ volumes_from:
+ description:
+ - Mount volumes from the specified container(s).
+ type: list
+ elements: str
+ workdir:
+ description:
+ - Working directory inside the container.
+ The default working directory for running binaries within a container
+ is the root directory (/).
+ type: str
+ aliases:
+ - working_dir
+"""
+
+EXAMPLES = r"""
+- name: Run container
+ containers.podman.podman_container:
+ name: container
+ image: quay.io/bitnami/wildfly
+ state: started
+
+- name: Create a data container
+ containers.podman.podman_container:
+ name: mydata
+ image: busybox
+ volume:
+ - /tmp/data
+
+- name: Re-create a redis container
+ containers.podman.podman_container:
+ name: myredis
+ image: redis
+ command: redis-server --appendonly yes
+ state: present
+ recreate: yes
+ expose:
+ - 6379
+ volumes_from:
+ - mydata
+
+- name: Restart a container
+ containers.podman.podman_container:
+ name: myapplication
+ image: redis
+ state: started
+ restart: yes
+ etc_hosts:
+ other: "127.0.0.1"
+ restart_policy: "no"
+ device: "/dev/sda:/dev/xvda:rwm"
+ ports:
+ - "8080:9000"
+ - "127.0.0.1:8081:9001/udp"
+ env:
+ SECRET_KEY: "ssssh"
+ BOOLEAN_KEY: "yes"
+
+- name: Container present
+ containers.podman.podman_container:
+ name: mycontainer
+ state: present
+ image: ubuntu:14.04
+ command: "sleep 1d"
+
+- name: Stop a container
+ containers.podman.podman_container:
+ name: mycontainer
+ state: stopped
+
+- name: Start 4 load-balanced containers
+ containers.podman.podman_container:
+ name: "container{{ item }}"
+ recreate: yes
+ image: someuser/anotherappimage
+ command: sleep 1d
+ with_sequence: count=4
+
+- name: remove container
+ containers.podman.podman_container:
+ name: ohno
+ state: absent
+
+- name: Writing output
+ containers.podman.podman_container:
+ name: myservice
+ image: busybox
+ log_options: path=/var/log/container/mycontainer.json
+ log_driver: k8s-file
+"""
+
+RETURN = r"""
+container:
+ description:
+ - Facts representing the current state of the container. Matches the
+ podman inspection output.
+ - Note that facts are part of the registered vars since Ansible 2.8. For
+ compatibility reasons, the facts
+ are also accessible directly as C(podman_container). Note that the
+ returned fact will be removed in Ansible 2.12.
+ - Empty if C(state) is I(absent).
+ returned: always
+ type: dict
+ sample: '{
+ "AppArmorProfile": "",
+ "Args": [
+ "sh"
+ ],
+ "BoundingCaps": [
+ "CAP_CHOWN",
+ ...
+ ],
+ "Config": {
+ "Annotations": {
+ "io.kubernetes.cri-o.ContainerType": "sandbox",
+ "io.kubernetes.cri-o.TTY": "false"
+ },
+ "AttachStderr": false,
+ "AttachStdin": false,
+ "AttachStdout": false,
+ "Cmd": [
+ "sh"
+ ],
+ "Domainname": "",
+ "Entrypoint": "",
+ "Env": [
+ "PATH=/usr/sbin:/usr/bin:/sbin:/bin",
+ "TERM=xterm",
+ "HOSTNAME=",
+ "container=podman"
+ ],
+ "Hostname": "",
+ "Image": "docker.io/library/busybox:latest",
+ "Labels": null,
+ "OpenStdin": false,
+ "StdinOnce": false,
+ "StopSignal": 15,
+ "Tty": false,
+ "User": {
+ "gid": 0,
+ "uid": 0
+ },
+ "Volumes": null,
+ "WorkingDir": "/"
+ },
+ "ConmonPidFile": "...",
+ "Created": "2019-06-17T19:13:09.873858307+03:00",
+ "Dependencies": [],
+ "Driver": "overlay",
+ "EffectiveCaps": [
+ "CAP_CHOWN",
+ ...
+ ],
+ "ExecIDs": [],
+ "ExitCommand": [
+ "/usr/bin/podman",
+ "--root",
+ ...
+ ],
+ "GraphDriver": {
+ ...
+ },
+ "HostConfig": {
+ ...
+ },
+ "HostnamePath": "...",
+ "HostsPath": "...",
+ "ID": "...",
+ "Image": "...",
+ "ImageName": "docker.io/library/busybox:latest",
+ "IsInfra": false,
+ "LogPath": "/tmp/container/mycontainer.json",
+ "MountLabel": "system_u:object_r:container_file_t:s0:c282,c782",
+ "Mounts": [
+ ...
+ ],
+ "Name": "myservice",
+ "Namespace": "",
+ "NetworkSettings": {
+ "Bridge": "",
+ ...
+ },
+ "Path": "sh",
+ "ProcessLabel": "system_u:system_r:container_t:s0:c282,c782",
+ "ResolvConfPath": "...",
+ "RestartCount": 0,
+ "Rootfs": "",
+ "State": {
+ "Dead": false,
+ "Error": "",
+ "ExitCode": 0,
+ "FinishedAt": "2019-06-17T19:13:10.157518963+03:00",
+ "Healthcheck": {
+ "FailingStreak": 0,
+ "Log": null,
+ "Status": ""
+ },
+ "OOMKilled": false,
+ "OciVersion": "1.0.1-dev",
+ "Paused": false,
+ "Pid": 4083,
+ "Restarting": false,
+ "Running": false,
+ "StartedAt": "2019-06-17T19:13:10.152479729+03:00",
+ "Status": "exited"
+ },
+ "StaticDir": "..."
+ ...
+ }'
+"""
+
+from ansible.module_utils.basic import AnsibleModule # noqa: F402
+from ..module_utils.podman.podman_container_lib import PodmanManager # noqa: F402
+from ..module_utils.podman.podman_container_lib import ARGUMENTS_SPEC_CONTAINER # noqa: F402
+
+
+def main():
+ module = AnsibleModule(
+ argument_spec=ARGUMENTS_SPEC_CONTAINER,
+ mutually_exclusive=(
+ ['no_hosts', 'etc_hosts'],
+ ),
+ supports_check_mode=True,
+ )
+
+ # work on input vars
+ if module.params['state'] in ['started', 'present'] and \
+ not module.params['image']:
+ module.fail_json(msg="State '%s' required image to be configured!" %
+ module.params['state'])
+
+ results = PodmanManager(module, module.params).execute()
+ module.exit_json(**results)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_container_info.py b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_container_info.py
new file mode 100644
index 00000000..d3ec55c8
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_container_info.py
@@ -0,0 +1,405 @@
+#!/usr/bin/python
+# Copyright (c) 2020 Red Hat
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+DOCUMENTATION = r'''
+module: podman_container_info
+author:
+ - Sagi Shnaidman (@sshnaidm)
+ - Emilien Macchi (@EmilienM)
+short_description: Gather facts about containers using podman
+notes:
+ - Podman may require elevated privileges in order to run properly.
+description:
+ - Gather facts about containers using C(podman)
+requirements:
+ - "Podman installed on host"
+options:
+ 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 = r"""
+- name: Gather facts for all containers
+ containers.podman.podman_container_info:
+
+- name: Gather facts on a specific container
+ containers.podman.podman_container_info:
+ name: web1
+
+- name: Gather facts on several containers
+ containers.podman.podman_container_info:
+ name:
+ - redis
+ - web1
+"""
+
+RETURN = r"""
+containers:
+ description: Facts from all or specificed containers
+ returned: always
+ type: list
+ elements: dict
+ 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": [
+ "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):
+ """Collect containers facts for all containers or for specified in 'name'.
+
+ Arguments:
+ module {AnsibleModule} -- instance of AnsibleModule
+ executable {string} -- binary to execute when inspecting containers
+ name {list} -- list of names or None in case of all containers
+
+ Returns:
+ list of containers info, stdout, stderr
+ """
+ if not name:
+ all_names = [executable, 'container', 'ls', '-q', '-a']
+ rc, out, err = module.run_command(all_names)
+ if rc != 0:
+ module.fail_json(msg="Unable to get list of containers: %s" % err)
+ name = out.split()
+ if not name:
+ return [], out, err
+ command = [executable, 'container', 'inspect']
+ command.extend(name)
+ rc, out, err = module.run_command(command)
+ if rc == 0:
+ json_out = json.loads(out) if out else None
+ if json_out is None:
+ return [], out, err
+ return json_out, out, err
+ if rc != 0 and 'no such ' in err:
+ if len(name) < 2:
+ return [], out, err
+ return cycle_over(module, executable, name)
+ module.fail_json(msg="Unable to gather info for %s: %s" % (",".join(name), err))
+
+
+def cycle_over(module, executable, name):
+ """Inspect each container in a cycle in case some of them don't exist.
+
+ Arguments:
+ module {AnsibleModule} -- instance of AnsibleModule
+ executable {string} -- binary to execute when inspecting containers
+ name {list} -- list of containers names to inspect
+
+ Returns:
+ list of containers info, stdout as empty, stderr
+ """
+ inspection = []
+ stderrs = []
+ for container in name:
+ command = [executable, 'container', 'inspect', container]
+ rc, out, err = module.run_command(command)
+ if rc != 0 and 'no such ' not in err:
+ module.fail_json(msg="Unable to gather info for %s: %s" % (container, err))
+ if rc == 0 and out:
+ json_out = json.loads(out)
+ if json_out:
+ inspection += json_out
+ stderrs.append(err)
+ return inspection, "", "\n".join(stderrs)
+
+
+def main():
+ module = AnsibleModule(
+ argument_spec={
+ 'executable': {'type': 'str', 'default': 'podman'},
+ 'name': {'type': 'list', 'elements': 'str'},
+ },
+ supports_check_mode=True,
+ )
+
+ name = module.params['name']
+ executable = module.get_bin_path(module.params['executable'], required=True)
+ # pylint: disable=unused-variable
+ inspect_results, out, err = get_containers_facts(module, executable, name)
+
+ results = {
+ "changed": False,
+ "containers": inspect_results,
+ "stderr": err
+ }
+
+ module.exit_json(**results)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_containers.py b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_containers.py
new file mode 100644
index 00000000..75ebb057
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_containers.py
@@ -0,0 +1,162 @@
+#!/usr/bin/python
+# Copyright (c) 2020 Red Hat
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+DOCUMENTATION = '''
+---
+module: podman_containers
+author:
+ - "Sagi Shnaidman (@sshnaidm)"
+version_added: '1.4.0'
+short_description: Manage podman containers in a batch
+description:
+ - Manage groups of podman containers
+requirements:
+ - "podman"
+options:
+ containers:
+ description:
+ - List of dictionaries with data for running containers for podman_container module.
+ required: True
+ type: list
+ elements: dict
+ debug:
+ description:
+ - Return additional information which can be helpful for investigations.
+ type: bool
+ default: False
+'''
+
+EXAMPLES = '''
+- name: Run three containers at once
+ podman_containers:
+ containers:
+ - name: alpine
+ image: alpine
+ command: sleep 1d
+ - name: web
+ image: nginx
+ - name: test
+ image: python:3-alpine
+ command: python -V
+'''
+
+from copy import deepcopy # noqa: F402
+
+from ansible.module_utils.basic import AnsibleModule # noqa: F402
+from ..module_utils.podman.podman_container_lib import PodmanManager # noqa: F402
+from ..module_utils.podman.podman_container_lib import ARGUMENTS_SPEC_CONTAINER # noqa: F402
+
+
+def init_options():
+ default = {}
+ opts = ARGUMENTS_SPEC_CONTAINER
+ for k, v in opts.items():
+ if 'default' in v:
+ default[k] = v['default']
+ else:
+ default[k] = None
+ return default
+
+
+def update_options(opts_dict, container):
+ aliases = {}
+ for k, v in ARGUMENTS_SPEC_CONTAINER.items():
+ if 'aliases' in v:
+ for alias in v['aliases']:
+ aliases[alias] = k
+ for k in list(container):
+ if k in aliases:
+ key = aliases[k]
+ opts_dict[key] = container[k]
+ container.pop(k)
+ opts_dict.update(container)
+ return opts_dict
+
+
+def combine(results):
+ changed = any([i.get('changed', False) for i in results])
+ failed = any([i.get('failed', False) for i in results])
+ actions = []
+ podman_actions = []
+ containers = []
+ podman_version = ''
+ diffs = {}
+ stderr = ''
+ stdout = ''
+ for i in results:
+ if 'actions' in i and i['actions']:
+ actions += i['actions']
+ if 'podman_actions' in i and i['podman_actions']:
+ podman_actions += i['podman_actions']
+ if 'container' in i and i['container']:
+ containers.append(i['container'])
+ if 'podman_version' in i:
+ podman_version = i['podman_version']
+ if 'diff' in i:
+ diffs[i['container']['Name']] = i['diff']
+ if 'stderr' in i:
+ stderr += i['stderr']
+ if 'stdout' in i:
+ stdout += i['stdout']
+
+ total = {
+ 'changed': changed,
+ 'failed': failed,
+ 'actions': actions,
+ 'podman_actions': podman_actions,
+ 'containers': containers,
+ 'stdout': stdout,
+ 'stderr': stderr,
+ }
+ if podman_version:
+ total['podman_version'] = podman_version
+ if diffs:
+ before = after = ''
+ for k, v in diffs.items():
+ before += "".join([str(k), ": ", str(v['before']), "\n"])
+ after += "".join([str(k), ": ", str(v['after']), "\n"])
+ total['diff'] = {
+ 'before': before,
+ 'after': after
+ }
+ return total
+
+
+def check_input_strict(container):
+ if container['state'] in ['started', 'present'] and not container['image']:
+ return "State '%s' required image to be configured!" % container['state']
+
+
+def main():
+ module = AnsibleModule(
+ argument_spec=dict(
+ containers=dict(type='list', elements='dict', required=True),
+ debug=dict(type='bool', default=False),
+ ),
+ supports_check_mode=True,
+ )
+ # work on input vars
+
+ results = []
+ default_options_templ = init_options()
+ for container in module.params['containers']:
+ options_dict = deepcopy(default_options_templ)
+ options_dict = update_options(options_dict, container)
+ options_dict['debug'] = module.params['debug'] or options_dict['debug']
+ test_input = check_input_strict(options_dict)
+ if test_input:
+ module.fail_json(
+ msg="Failed to run container %s because: %s" % (options_dict['name'], test_input))
+ res = PodmanManager(module, options_dict).execute()
+ results.append(res)
+ total_results = combine(results)
+ module.exit_json(**total_results)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_image.py b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_image.py
new file mode 100644
index 00000000..186f0498
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_image.py
@@ -0,0 +1,774 @@
+#!/usr/bin/python
+# Copyright (c) 2018 Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+DOCUMENTATION = r'''
+ module: podman_image
+ author:
+ - Sam Doran (@samdoran)
+ short_description: Pull images for use by podman
+ notes: []
+ description:
+ - Build, pull, or push images using Podman.
+ options:
+ name:
+ description:
+ - Name of the image to pull, push, or delete. It may contain a tag using the format C(image:tag).
+ required: True
+ type: 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
+ ca_cert_dir:
+ description:
+ - Path to directory containing TLS certificates and keys to use.
+ type: 'path'
+ tag:
+ description:
+ - Tag of the image to pull, push, or delete.
+ default: "latest"
+ type: str
+ pull:
+ description: Whether or not to pull the image.
+ default: True
+ type: bool
+ push:
+ description: Whether or not to push an image.
+ default: False
+ type: bool
+ path:
+ description: Path to directory containing the build file.
+ type: str
+ force:
+ description:
+ - Whether or not to force push or pull an image.
+ - When building, force the build even if the image already exists.
+ type: bool
+ default: False
+ state:
+ description:
+ - Whether an image should be present, absent, or built.
+ default: "present"
+ type: str
+ choices:
+ - present
+ - absent
+ - build
+ validate_certs:
+ description:
+ - Require HTTPS and validate certificates when pulling or pushing. Also used during build if a pull or push is necessary.
+ default: True
+ type: bool
+ aliases:
+ - tlsverify
+ - tls_verify
+ password:
+ description:
+ - Password to use when authenticating to remote registries.
+ type: str
+ username:
+ description:
+ - username to use when authenticating to remote registries.
+ type: str
+ auth_file:
+ description:
+ - Path to file containing authorization credentials to the remote registry.
+ aliases:
+ - authfile
+ type: path
+ build:
+ description: Arguments that control image build.
+ type: dict
+ aliases:
+ - build_args
+ - buildargs
+ suboptions:
+ volume:
+ description:
+ - Specify multiple volume / mount options to mount one or more mounts to a container.
+ type: list
+ elements: str
+ annotation:
+ description:
+ - Dictionary of key=value pairs to add to the image. Only works with OCI images. Ignored for Docker containers.
+ type: dict
+ force_rm:
+ description:
+ - Always remove intermediate containers after a build, even if the build is unsuccessful.
+ type: bool
+ default: False
+ format:
+ description:
+ - Format of the built image.
+ type: str
+ choices:
+ - docker
+ - oci
+ default: "oci"
+ cache:
+ description:
+ - Whether or not to use cached layers when building an image
+ type: bool
+ default: True
+ rm:
+ description: Remove intermediate containers after a successful build
+ type: bool
+ default: True
+ extra_args:
+ description:
+ - Extra args to pass to build, if executed. Does not idempotently check for new build args.
+ type: str
+ push_args:
+ description: Arguments that control pushing images.
+ type: dict
+ suboptions:
+ compress:
+ description:
+ - Compress tarball image layers when pushing to a directory using the 'dir' transport.
+ type: bool
+ format:
+ description:
+ - Manifest type to use when pushing an image using the 'dir' transport (default is manifest type of source).
+ type: str
+ choices:
+ - oci
+ - v2s1
+ - v2s2
+ remove_signatures:
+ description: Discard any pre-existing signatures in the image
+ type: bool
+ sign_by:
+ description:
+ - Path to a key file to use to sign the image.
+ type: str
+ dest:
+ description: Path or URL where image will be pushed.
+ type: str
+ aliases:
+ - destination
+ transport:
+ description:
+ - Transport to use when pushing in image. If no transport is set, will attempt to push to a remote registry.
+ type: str
+ choices:
+ - dir
+ - docker-archive
+ - docker-daemon
+ - oci-archive
+ - ostree
+'''
+
+EXAMPLES = r"""
+- name: Pull an image
+ container.podman.podman_image:
+ name: quay.io/bitnami/wildfly
+
+- name: Remove an image
+ container.podman.podman_image:
+ name: quay.io/bitnami/wildfly
+ state: absent
+
+- name: Pull a specific version of an image
+ container.podman.podman_image:
+ name: redis
+ tag: 4
+
+- name: Build a basic OCI image
+ container.podman.podman_image:
+ name: nginx
+ path: /path/to/build/dir
+
+- name: Build a basic OCI image with advanced parameters
+ container.podman.podman_image:
+ name: nginx
+ path: /path/to/build/dir
+ build:
+ cache: no
+ force_rm: yes
+ format: oci
+ annotation:
+ app: nginx
+ function: proxy
+ info: Load balancer for my cool app
+
+- name: Build a Docker formatted image
+ container.podman.podman_image:
+ name: nginx
+ path: /path/to/build/dir
+ build:
+ format: docker
+
+- name: Build and push an image using existing credentials
+ container.podman.podman_image:
+ name: nginx
+ path: /path/to/build/dir
+ push: yes
+ push_args:
+ dest: quay.io/acme
+
+- name: Build and push an image using an auth file
+ container.podman.podman_image:
+ name: nginx
+ push: yes
+ auth_file: /etc/containers/auth.json
+ push_args:
+ dest: quay.io/acme
+
+- name: Build and push an image using username and password
+ container.podman.podman_image:
+ name: nginx
+ push: yes
+ username: bugs
+ password: "{{ vault_registry_password }}"
+ push_args:
+ dest: quay.io/acme
+
+- name: Build and push an image to multiple registries
+ container.podman.podman_image:
+ name: "{{ item }}"
+ path: /path/to/build/dir
+ push: yes
+ auth_file: /etc/containers/auth.json
+ loop:
+ - quay.io/acme/nginx
+ - docker.io/acme/nginx
+
+- name: Build and push an image to multiple registries with separate parameters
+ container.podman.podman_image:
+ name: "{{ item.name }}"
+ tag: "{{ item.tag }}"
+ path: /path/to/build/dir
+ push: yes
+ auth_file: /etc/containers/auth.json
+ push_args:
+ dest: "{{ item.dest }}"
+ loop:
+ - name: nginx
+ tag: 4
+ dest: docker.io/acme
+
+ - name: nginx
+ tag: 3
+ dest: docker.io/acme
+"""
+
+RETURN = r"""
+ image:
+ description:
+ - Image inspection results for the image that was pulled, pushed, or built.
+ returned: success
+ type: dict
+ sample: [
+ {
+ "Annotations": {},
+ "Architecture": "amd64",
+ "Author": "",
+ "Comment": "from Bitnami with love",
+ "ContainerConfig": {
+ "Cmd": [
+ "/run.sh"
+ ],
+ "Entrypoint": [
+ "/app-entrypoint.sh"
+ ],
+ "Env": [
+ "PATH=/opt/bitnami/java/bin:/opt/bitnami/wildfly/bin:/opt/bitnami/nami/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
+ "IMAGE_OS=debian-9",
+ "NAMI_VERSION=1.0.0-1",
+ "GPG_KEY_SERVERS_LIST=ha.pool.sks-keyservers.net",
+ "TINI_VERSION=v0.13.2",
+ "TINI_GPG_KEY=595E85A6B1B4779EA4DAAEC70B588DFF0527A9B7",
+ "GOSU_VERSION=1.10",
+ "GOSU_GPG_KEY=B42F6819007F00F88E364FD4036A9C25BF357DD4",
+ "BITNAMI_IMAGE_VERSION=16.0.0-debian-9-r27",
+ "BITNAMI_PKG_CHMOD=-R g+rwX",
+ "BITNAMI_PKG_EXTRA_DIRS=/home/wildfly",
+ "HOME=/",
+ "BITNAMI_APP_NAME=wildfly",
+ "NAMI_PREFIX=/.nami",
+ "WILDFLY_HOME=/home/wildfly",
+ "WILDFLY_JAVA_HOME=",
+ "WILDFLY_JAVA_OPTS=",
+ "WILDFLY_MANAGEMENT_HTTP_PORT_NUMBER=9990",
+ "WILDFLY_PASSWORD=bitnami",
+ "WILDFLY_PUBLIC_CONSOLE=true",
+ "WILDFLY_SERVER_AJP_PORT_NUMBER=8009",
+ "WILDFLY_SERVER_HTTP_PORT_NUMBER=8080",
+ "WILDFLY_SERVER_INTERFACE=0.0.0.0",
+ "WILDFLY_USERNAME=user",
+ "WILDFLY_WILDFLY_HOME=/home/wildfly",
+ "WILDFLY_WILDFLY_OPTS=-Dwildfly.as.deployment.ondemand=false"
+ ],
+ "ExposedPorts": {
+ "8080/tcp": {},
+ "9990/tcp": {}
+ },
+ "Labels": {
+ "maintainer": "Bitnami <containers@bitnami.com>"
+ },
+ "User": "1001"
+ },
+ "Created": "2019-04-10T05:48:03.553887623Z",
+ "Digest": "sha256:5a8ab28e314c2222de3feaf6dac94a0436a37fc08979d2722c99d2bef2619a9b",
+ "GraphDriver": {
+ "Data": {
+ "LowerDir": "/var/lib/containers/storage/overlay/142c1beadf1bb09fbd929465ec98c9dca3256638220450efb4214727d0d0680e/diff:/var/lib/containers/s",
+ "MergedDir": "/var/lib/containers/storage/overlay/9aa10191f5bddb59e28508e721fdeb43505e5b395845fa99723ed787878dbfea/merged",
+ "UpperDir": "/var/lib/containers/storage/overlay/9aa10191f5bddb59e28508e721fdeb43505e5b395845fa99723ed787878dbfea/diff",
+ "WorkDir": "/var/lib/containers/storage/overlay/9aa10191f5bddb59e28508e721fdeb43505e5b395845fa99723ed787878dbfea/work"
+ },
+ "Name": "overlay"
+ },
+ "History": [
+ {
+ "comment": "from Bitnami with love",
+ "created": "2019-04-09T22:27:40.659377677Z"
+ },
+ {
+ "created": "2019-04-09T22:38:53.86336555Z",
+ "created_by": "/bin/sh -c #(nop) LABEL maintainer=Bitnami <containers@bitnami.com>",
+ "empty_layer": true
+ },
+ {
+ "created": "2019-04-09T22:38:54.022778765Z",
+ "created_by": "/bin/sh -c #(nop) ENV IMAGE_OS=debian-9",
+ "empty_layer": true
+ },
+ ],
+ "Id": "ace34da54e4af2145e1ad277005adb235a214e4dfe1114c2db9ab460b840f785",
+ "Labels": {
+ "maintainer": "Bitnami <containers@bitnami.com>"
+ },
+ "ManifestType": "application/vnd.docker.distribution.manifest.v1+prettyjws",
+ "Os": "linux",
+ "Parent": "",
+ "RepoDigests": [
+ "quay.io/bitnami/wildfly@sha256:5a8ab28e314c2222de3feaf6dac94a0436a37fc08979d2722c99d2bef2619a9b"
+ ],
+ "RepoTags": [
+ "quay.io/bitnami/wildfly:latest"
+ ],
+ "RootFS": {
+ "Layers": [
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ "",
+ ""
+ ],
+ "Type": "layers"
+ },
+ "Size": 466180019,
+ "User": "1001",
+ "Version": "18.09.3",
+ "VirtualSize": 466180019
+ }
+ ]
+"""
+
+import json
+import re
+import shlex
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible_collections.containers.podman.plugins.module_utils.podman.common import run_podman_command
+
+
+class PodmanImageManager(object):
+
+ def __init__(self, module, results):
+
+ super(PodmanImageManager, self).__init__()
+
+ self.module = module
+ self.results = results
+ self.name = self.module.params.get('name')
+ self.executable = self.module.get_bin_path(module.params.get('executable'), required=True)
+ self.tag = self.module.params.get('tag')
+ self.pull = self.module.params.get('pull')
+ self.push = self.module.params.get('push')
+ self.path = self.module.params.get('path')
+ self.force = self.module.params.get('force')
+ self.state = self.module.params.get('state')
+ self.validate_certs = self.module.params.get('validate_certs')
+ self.auth_file = self.module.params.get('auth_file')
+ self.username = self.module.params.get('username')
+ self.password = self.module.params.get('password')
+ self.ca_cert_dir = self.module.params.get('ca_cert_dir')
+ self.build = self.module.params.get('build')
+ self.push_args = self.module.params.get('push_args')
+
+ repo, repo_tag = parse_repository_tag(self.name)
+ if repo_tag:
+ self.name = repo
+ self.tag = repo_tag
+
+ self.image_name = '{name}:{tag}'.format(name=self.name, tag=self.tag)
+
+ if self.state in ['present', 'build']:
+ self.present()
+
+ if self.state in ['absent']:
+ self.absent()
+
+ def _run(self, args, expected_rc=0, ignore_errors=False):
+ return run_podman_command(
+ module=self.module,
+ executable=self.executable,
+ args=args,
+ expected_rc=expected_rc,
+ ignore_errors=ignore_errors)
+
+ def _get_id_from_output(self, lines, startswith=None, contains=None, split_on=' ', maxsplit=1):
+ layer_ids = []
+ for line in lines.splitlines():
+ if startswith and line.startswith(startswith) or contains and contains in line:
+ splitline = line.rsplit(split_on, maxsplit)
+ layer_ids.append(splitline[1])
+
+ # Podman 1.4 changed the output to only include the layer id when run in quiet mode
+ if not layer_ids:
+ layer_ids = lines.splitlines()
+
+ return(layer_ids[-1])
+
+ def present(self):
+ image = self.find_image()
+
+ if image:
+ digest_before = image[0].get('Digest', image[0].get('digest'))
+ else:
+ digest_before = None
+
+ if not image or self.force:
+ if self.path:
+ # Build the image
+ self.results['actions'].append('Built image {image_name} from {path}'.format(image_name=self.image_name, path=self.path))
+ if not self.module.check_mode:
+ image = self.results['image'] = self.build_image()
+ else:
+ # Pull the image
+ self.results['actions'].append('Pulled image {image_name}'.format(image_name=self.image_name))
+ if not self.module.check_mode:
+ image = self.results['image'] = self.pull_image()
+
+ if not image:
+ image = self.find_image()
+ digest_after = image[0].get('Digest', image[0].get('digest'))
+ self.results['changed'] = digest_before != digest_after
+
+ if self.push:
+ # Push the image
+ if '/' in self.image_name:
+ push_format_string = 'Pushed image {image_name}'
+ else:
+ push_format_string = 'Pushed image {image_name} to {dest}'
+ self.results['actions'].append(push_format_string.format(image_name=self.image_name, dest=self.push_args['dest']))
+ self.results['changed'] = True
+ if not self.module.check_mode:
+ self.results['image'] = self.push_image()
+
+ def absent(self):
+ image = self.find_image()
+
+ if image:
+ self.results['actions'].append('Removed image {name}'.format(name=self.name))
+ self.results['changed'] = True
+ self.results['image']['state'] = 'Deleted'
+ if not self.module.check_mode:
+ self.remove_image()
+
+ def find_image(self, image_name=None):
+ if image_name is None:
+ image_name = self.image_name
+ args = ['image', 'ls', image_name, '--format', 'json']
+ rc, images, err = self._run(args, ignore_errors=True)
+ if len(images) > 0:
+ return json.loads(images)
+ else:
+ return None
+
+ def inspect_image(self, image_name=None):
+ if image_name is None:
+ image_name = self.image_name
+ args = ['inspect', image_name, '--format', 'json']
+ rc, image_data, err = self._run(args)
+ if len(image_data) > 0:
+ return json.loads(image_data)
+ else:
+ return None
+
+ def pull_image(self, image_name=None):
+ if image_name is None:
+ image_name = self.image_name
+
+ args = ['pull', image_name, '-q']
+
+ if self.auth_file:
+ args.extend(['--authfile', self.auth_file])
+
+ if self.username and self.password:
+ cred_string = '{user}:{password}'.format(user=self.username, password=self.password)
+ args.extend(['--creds', cred_string])
+
+ if self.validate_certs is not None:
+ if self.validate_certs:
+ args.append('--tls-verify')
+ else:
+ args.append('--tls-verify=false')
+
+ if self.ca_cert_dir:
+ args.extend(['--cert-dir', self.ca_cert_dir])
+
+ rc, out, err = self._run(args, ignore_errors=True)
+ if rc != 0:
+ self.module.fail_json(msg='Failed to pull image {image_name}'.format(image_name=image_name))
+ return self.inspect_image(out.strip())
+
+ def build_image(self):
+ args = ['build', '-q']
+ args.extend(['-t', self.image_name])
+
+ if self.validate_certs is not None:
+ if self.validate_certs:
+ args.append('--tls-verify')
+ else:
+ args.append('--tls-verify=false')
+
+ annotation = self.build.get('annotation')
+ if annotation:
+ for k, v in annotation.items():
+ args.extend(['--annotation', '{k}={v}'.format(k=k, v=v)])
+
+ if self.ca_cert_dir:
+ args.extend(['--cert-dir', self.ca_cert_dir])
+
+ if self.build.get('force_rm'):
+ args.append('--force-rm')
+
+ image_format = self.build.get('format')
+ if image_format:
+ args.extend(['--format', image_format])
+
+ if not self.build.get('cache'):
+ args.append('--no-cache')
+
+ if self.build.get('rm'):
+ args.append('--rm')
+
+ volume = self.build.get('volume')
+ if volume:
+ for v in volume:
+ args.extend(['--volume', v])
+
+ if self.auth_file:
+ args.extend(['--authfile', self.auth_file])
+
+ if self.username and self.password:
+ cred_string = '{user}:{password}'.format(user=self.username, password=self.password)
+ args.extend(['--creds', cred_string])
+
+ extra_args = self.build.get('extra_args')
+ if extra_args:
+ args.extend([arg for arg in shlex.split(extra_args)])
+
+ args.append(self.path)
+
+ rc, out, err = self._run(args, ignore_errors=True)
+ if rc != 0:
+ self.module.fail_json(msg="Failed to build image {image}: {out} {err}".format(image=self.image_name, out=out, err=err))
+
+ last_id = self._get_id_from_output(out, startswith='-->')
+ return self.inspect_image(last_id)
+
+ def push_image(self):
+ args = ['push']
+
+ if self.validate_certs is not None:
+ if self.validate_certs:
+ args.append('--tls-verify')
+ else:
+ args.append('--tls-verify=false')
+
+ if self.ca_cert_dir:
+ args.extend(['--cert-dir', self.ca_cert_dir])
+
+ if self.username and self.password:
+ cred_string = '{user}:{password}'.format(user=self.username, password=self.password)
+ args.extend(['--creds', cred_string])
+
+ if self.auth_file:
+ args.extend(['--authfile', self.auth_file])
+
+ if self.push_args.get('compress'):
+ args.append('--compress')
+
+ push_format = self.push_args.get('format')
+ if push_format:
+ args.extend(['--format', push_format])
+
+ if self.push_args.get('remove_signatures'):
+ args.append('--remove-signatures')
+
+ sign_by_key = self.push_args.get('sign_by')
+ if sign_by_key:
+ args.extend(['--sign-by', sign_by_key])
+
+ args.append(self.image_name)
+
+ # Build the destination argument
+ dest = self.push_args.get('dest')
+ dest_format_string = '{dest}/{image_name}'
+ regexp = re.compile(r'/{name}(:{tag})?'.format(name=self.name, tag=self.tag))
+ if not dest:
+ if '/' not in self.name:
+ self.module.fail_json(msg="'push_args['dest']' is required when pushing images that do not have the remote registry in the image name")
+
+ # If the push destination contains the image name and/or the tag
+ # remove it and warn since it's not needed.
+ elif regexp.search(dest):
+ dest = regexp.sub('', dest)
+ self.module.warn("Image name and tag are automatically added to push_args['dest']. Destination changed to {dest}".format(dest=dest))
+
+ if dest and dest.endswith('/'):
+ dest = dest[:-1]
+
+ transport = self.push_args.get('transport')
+ if transport:
+ if not dest:
+ self.module.fail_json("'push_args['transport'] requires 'push_args['dest'] but it was not provided.")
+ if transport == 'docker':
+ dest_format_string = '{transport}://{dest}'
+ elif transport == 'ostree':
+ dest_format_string = '{transport}:{name}@{dest}'
+ else:
+ dest_format_string = '{transport}:{dest}'
+
+ dest_string = dest_format_string.format(transport=transport, name=self.name, dest=dest, image_name=self.image_name,)
+
+ # Only append the destination argument if the image name is not a URL
+ if '/' not in self.name:
+ args.append(dest_string)
+
+ rc, out, err = self._run(args, ignore_errors=True)
+ if rc != 0:
+ self.module.fail_json(msg="Failed to push image {image_name}: {err}".format(image_name=self.image_name, err=err))
+ last_id = self._get_id_from_output(
+ out + err, contains=':', split_on=':')
+
+ return self.inspect_image(last_id)
+
+ def remove_image(self, image_name=None):
+ if image_name is None:
+ image_name = self.image_name
+
+ args = ['rmi', image_name]
+ if self.force:
+ args.append('--force')
+ rc, out, err = self._run(args, ignore_errors=True)
+ if rc != 0:
+ self.module.fail_json(msg='Failed to remove image {image_name}. {err}'.format(image_name=image_name, err=err))
+ return out
+
+
+def parse_repository_tag(repo_name):
+ parts = repo_name.rsplit('@', 1)
+ if len(parts) == 2:
+ return tuple(parts)
+ parts = repo_name.rsplit(':', 1)
+ if len(parts) == 2 and '/' not in parts[1]:
+ return tuple(parts)
+ return repo_name, None
+
+
+def main():
+ module = AnsibleModule(
+ argument_spec=dict(
+ name=dict(type='str', required=True),
+ tag=dict(type='str', default='latest'),
+ pull=dict(type='bool', default=True),
+ push=dict(type='bool', default=False),
+ path=dict(type='str'),
+ force=dict(type='bool', default=False),
+ state=dict(type='str', default='present', choices=['absent', 'present', 'build']),
+ validate_certs=dict(type='bool', default=True, aliases=['tlsverify', 'tls_verify']),
+ executable=dict(type='str', default='podman'),
+ auth_file=dict(type='path', aliases=['authfile']),
+ username=dict(type='str'),
+ password=dict(type='str', no_log=True),
+ ca_cert_dir=dict(type='path'),
+ build=dict(
+ type='dict',
+ aliases=['build_args', 'buildargs'],
+ default={},
+ options=dict(
+ annotation=dict(type='dict'),
+ force_rm=dict(type='bool', default=False),
+ format=dict(
+ type='str',
+ choices=['oci', 'docker'],
+ default='oci'
+ ),
+ cache=dict(type='bool', default=True),
+ rm=dict(type='bool', default=True),
+ volume=dict(type='list', elements='str'),
+ extra_args=dict(type='str'),
+ ),
+ ),
+ push_args=dict(
+ type='dict',
+ default={},
+ options=dict(
+ compress=dict(type='bool'),
+ format=dict(type='str', choices=['oci', 'v2s1', 'v2s2']),
+ remove_signatures=dict(type='bool'),
+ sign_by=dict(type='str'),
+ dest=dict(type='str', aliases=['destination'],),
+ transport=dict(
+ type='str',
+ choices=[
+ 'dir',
+ 'docker-archive',
+ 'docker-daemon',
+ 'oci-archive',
+ 'ostree',
+ ]
+ ),
+ ),
+ ),
+ ),
+ supports_check_mode=True,
+ required_together=(
+ ['username', 'password'],
+ ),
+ mutually_exclusive=(
+ ['authfile', 'username'],
+ ['authfile', 'password'],
+ ),
+ )
+
+ results = dict(
+ changed=False,
+ actions=[],
+ image={},
+ )
+
+ PodmanImageManager(module, results)
+ module.exit_json(**results)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_image_info.py b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_image_info.py
new file mode 100644
index 00000000..4712c56a
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_image_info.py
@@ -0,0 +1,234 @@
+#!/usr/bin/python
+# Copyright (c) 2019 Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+DOCUMENTATION = r'''
+module: podman_image_info
+author:
+ - Sam Doran (@samdoran)
+short_description: Gather info about images using podman
+notes:
+ - Podman may required elevated privileges in order to run properly.
+description:
+ - Gather info about images using C(podman)
+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 info about. If no name is given return info about all images.
+ type: list
+ elements: str
+
+'''
+
+EXAMPLES = r"""
+- name: Gather info for all images
+ containers.podman.podman_image_info:
+
+- name: Gather info on a specific image
+ containers.podman.podman_image_info:
+ name: nginx
+
+- name: Gather info on several images
+ containers.podman.podman_image_info:
+ name:
+ - redis
+ - quay.io/bitnami/wildfly
+"""
+
+RETURN = r"""
+images:
+ description: info from all or specified images
+ returned: always
+ type: dict
+ sample: [
+ {
+ "Annotations": {},
+ "Architecture": "amd64",
+ "Author": "",
+ "Comment": "from Bitnami with love",
+ "ContainerConfig": {
+ "Cmd": [
+ "nami",
+ "start",
+ "--foreground",
+ "wildfly"
+ ],
+ "Entrypoint": [
+ "/app-entrypoint.sh"
+ ],
+ "Env": [
+ "PATH=/opt/bitnami/java/bin:/opt/bitnami/wildfly/bin:/opt/bitnami/nami/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
+ "IMAGE_OS=debian-9",
+ "NAMI_VERSION=0.0.9-0",
+ "GPG_KEY_SERVERS_LIST=ha.pool.sks-keyservers.net \
+hkp://p80.pool.sks-keyservers.net:80 keyserver.ubuntu.com hkp://keyserver.ubuntu.com:80 pgp.mit.edu",
+ "TINI_VERSION=v0.13.2",
+ "TINI_GPG_KEY=595E85A6B1B4779EA4DAAEC70B588DFF0527A9B7",
+ "GOSU_VERSION=1.10",
+ "GOSU_GPG_KEY=B42F6819007F00F88E364FD4036A9C25BF357DD4",
+ "BITNAMI_IMAGE_VERSION=14.0.1-debian-9-r12",
+ "BITNAMI_APP_NAME=wildfly",
+ "WILDFLY_JAVA_HOME=",
+ "WILDFLY_JAVA_OPTS=",
+ "WILDFLY_MANAGEMENT_HTTP_PORT_NUMBER=9990",
+ "WILDFLY_PASSWORD=bitnami",
+ "WILDFLY_PUBLIC_CONSOLE=true",
+ "WILDFLY_SERVER_AJP_PORT_NUMBER=8009",
+ "WILDFLY_SERVER_HTTP_PORT_NUMBER=8080",
+ "WILDFLY_SERVER_INTERFACE=0.0.0.0",
+ "WILDFLY_USERNAME=user",
+ "WILDFLY_WILDFLY_HOME=/home/wildfly",
+ "WILDFLY_WILDFLY_OPTS=-Dwildfly.as.deployment.ondemand=false"
+ ],
+ "ExposedPorts": {
+ "8080/tcp": {},
+ "9990/tcp": {}
+ },
+ "Labels": {
+ "maintainer": "Bitnami <containers@bitnami.com>"
+ }
+ },
+ "Created": "2018-09-25T04:07:45.934395523Z",
+ "Digest": "sha256:5c7d8e2dd66dcf4a152a4032a1d3c5a33458c67e1c1335edd8d18d738892356b",
+ "GraphDriver": {
+ "Data": {
+ "LowerDir": "/var/lib/containers/storage/overlay/a9dbf5616cc16919a8ac0dfc60aff87a72b5be52994c4649fcc91a089a12931\
+f/diff:/var/lib/containers/storage/overlay/67129bd46022122a7d8b7acb490092af6c7ce244ce4fbd7d9e2d2b7f5979e090/diff:/var/lib/containers/storage/overlay/7c51242c\
+4c5db5c74afda76d7fdbeab6965d8b21804bb3fc597dee09c770b0ca/diff:/var/lib/containers/storage/overlay/f97315dc58a9c002ba0cabccb9933d4b0d2113733d204188c88d72f75569b57b/diff:/var/lib/containers/storage/overlay/1dbde2dd497ddde2b467727125b900958a051a72561e58d29abe3d660dcaa9a7/diff:/var/lib/containers/storage/overlay/4aad9d80f30c3f0608f58173558b7554d84dee4dc4479672926eca29f75e6e33/diff:/var/lib/containers/storage/overlay/6751fc9b6868254870c062d75a511543fc8cfda2ce6262f4945f107449219632/diff:/var/lib/containers/storage/overlay/a27034d79081347421dd24d7e9e776c18271cd9a6e51053cb39af4d3d9c400e8/diff:/var/lib/containers/storage/overlay/537cf0045ed9cd7989f7944e7393019c81b16c1799a2198d8348cd182665397f/diff:/var/lib/containers/storage/overlay/27578615c5ae352af4e8449862d61aaf5c11b105a7d5905af55bd01b0c656d6e/diff:/var/lib/containers/storage/overlay/566542742840fe3034b3596f7cb9e62a6274c95a69f368f9e713746f8712c0b6/diff",
+ "MergedDir": "/var/lib/containers/storage/overlay/72bb96d6\
+c53ad57a0b1e44cab226a6251598accbead40b23fac89c19ad8c25ca/merged",
+ "UpperDir": "/var/lib/containers/storage/overlay/72bb96d6c53ad57a0b1e44cab226a6251598accbead40b23fac89c19ad8c25ca/diff",
+ "WorkDir": "/var/lib/containers/storage/overlay/72bb96d6c53ad57a0b1e44cab226a6251598accbead40b23fac89c19ad8c25ca/work"
+ },
+ "Name": "overlay"
+ },
+ "Id": "bcacbdf7a119c0fa934661ca8af839e625ce6540d9ceb6827cdd389f823d49e0",
+ "Labels": {
+ "maintainer": "Bitnami <containers@bitnami.com>"
+ },
+ "ManifestType": "application/vnd.docker.distribution.manifest.v1+prettyjws",
+ "Os": "linux",
+ "Parent": "",
+ "RepoDigests": [
+ "quay.io/bitnami/wildfly@sha256:5c7d8e2dd66dcf4a152a4032a1d3c5a33458c67e1c1335edd8d18d738892356b"
+ ],
+ "RepoTags": [
+ "quay.io/bitnami/wildfly:latest"
+ ],
+ "RootFS": {
+ "Layers": [
+ "sha256:75391df2c87e076b0c2f72d20c95c57dc8be7ee684cc07273416cce622b43367",
+ "sha256:7dd303f041039bfe8f0833092673ac35f93137d10e0fbc4302021ea65ad57731",
+ "sha256:720d9edf0cd2a9bb56b88b80be9070dbfaad359514c70094c65066963fed485d",
+ "sha256:6a567ecbf97725501a634fcb486271999aa4591b633b4ae9932a46b40f5aaf47",
+ "sha256:59e9a6db8f178f3da868614564faabb2820cdfb69be32e63a4405d6f7772f68c",
+ "sha256:310a82ccb092cd650215ab375da8943d235a263af9a029b8ac26a281446c04db",
+ "sha256:36cb91cf4513543a8f0953fed785747ea18b675bc2677f3839889cfca0aac79e"
+ ],
+ "Type": "layers"
+ },
+ "Size": 569919342,
+ "User": "",
+ "Version": "17.06.0-ce",
+ "VirtualSize": 569919342
+ }
+ ]
+"""
+
+import json
+
+from ansible.module_utils.basic import AnsibleModule
+
+
+def image_exists(module, executable, name):
+ command = [executable, 'image', 'exists', name]
+ rc, out, err = module.run_command(command)
+ if rc == 1:
+ return False
+ elif 'Command "exists" not found' in err:
+ # The 'exists' test is available in podman >= 0.12.1
+ command = [executable, 'image', 'ls', '-q', name]
+ rc2, out2, err2 = module.run_command(command)
+ if rc2 != 0:
+ return False
+ return True
+
+
+def filter_invalid_names(module, executable, name):
+ valid_names = []
+ names = name
+ if not isinstance(name, list):
+ names = [name]
+
+ for name in names:
+ if image_exists(module, executable, name):
+ valid_names.append(name)
+
+ return valid_names
+
+
+def get_image_info(module, executable, name):
+ names = name
+ if not isinstance(name, list):
+ names = [name]
+
+ if len(names) > 0:
+ command = [executable, 'image', 'inspect']
+ command.extend(names)
+ rc, out, err = module.run_command(command)
+
+ if rc != 0:
+ module.fail_json(msg="Unable to gather info for '{0}': {1}".format(', '.join(names), err))
+ return out
+
+ else:
+ return json.dumps([])
+
+
+def get_all_image_info(module, executable):
+ command = [executable, 'image', 'ls', '-q']
+ rc, out, err = module.run_command(command)
+ name = out.strip().split('\n')
+ out = get_image_info(module, executable, name)
+
+ return out
+
+
+def main():
+ module = AnsibleModule(
+ 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)
+
+ if name:
+ valid_names = filter_invalid_names(module, executable, name)
+ results = json.loads(get_image_info(module, executable, valid_names))
+ else:
+ results = json.loads(get_all_image_info(module, executable))
+
+ results = dict(
+ changed=False,
+ images=results
+ )
+
+ module.exit_json(**results)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_login_info.py b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_login_info.py
new file mode 100644
index 00000000..0ff72e43
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_login_info.py
@@ -0,0 +1,117 @@
+#!/usr/bin/python
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+DOCUMENTATION = r"""
+module: podman_login_info
+author:
+ - "Clemens Lange (@clelange)"
+version_added: '1.0.0'
+short_description: Return the logged-in user if any for a given registry
+notes: []
+description:
+ - Return the logged-in user if any for a given registry.
+requirements:
+ - "Podman installed on host"
+options:
+ registry:
+ description:
+ - Registry server.
+ type: str
+ required: true
+ authfile:
+ description:
+ - Path of the authentication file. Default is
+ ``${XDG_RUNTIME_DIR}/containers/auth.json``
+ (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: path
+ 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 = r"""
+- name: Return the logged-in user for docker hub registry
+ containers.podman.podman_login_info:
+ registry: docker.io
+
+- name: Return the logged-in user for quay.io registry
+ containers.podman.podman_login_info:
+ registry: quay.io
+"""
+
+RETURN = r"""
+login:
+ description: Logged in user for a registry
+ returned: always
+ type: dict
+ sample: {
+ "logged_in": true,
+ "registry": "docker.io",
+ "username": "clelange",
+ }
+"""
+
+import json
+from ansible.module_utils.basic import AnsibleModule
+
+
+def get_login_info(module, executable, authfile, registry):
+ command = [executable, 'login', '--get-login']
+ result = dict(
+ registry=registry,
+ username='',
+ logged_in=False,
+ )
+ if authfile:
+ command.extend(['--authfile', authfile])
+ if registry:
+ command.append(registry)
+ rc, out, err = module.run_command(command)
+ if rc != 0:
+ if 'Error: not logged into' in err:
+ # The error message is e.g. 'Error: not logged into docker.io'
+ # Therefore get last word to extract registry name
+ result["registry"] = err.split()[-1]
+ err = ''
+ return result
+ module.fail_json(msg="Unable to gather info for %s: %s" % (registry, err))
+ result["username"] = out.strip()
+ result["logged_in"] = True
+ return result
+
+
+def main():
+ module = AnsibleModule(
+ argument_spec=dict(
+ executable=dict(type='str', default='podman'),
+ authfile=dict(type='path'),
+ registry=dict(type='str', required=True)
+ ),
+ supports_check_mode=True,
+ )
+
+ registry = module.params['registry']
+ authfile = module.params['authfile']
+ executable = module.get_bin_path(module.params['executable'], required=True)
+
+ inspect_results = get_login_info(module, executable, authfile, registry)
+
+ results = {
+ "changed": False,
+ "login": inspect_results,
+ }
+
+ module.exit_json(**results)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_logout.py b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_logout.py
new file mode 100644
index 00000000..35627e8e
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_logout.py
@@ -0,0 +1,154 @@
+#!/usr/bin/python
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+DOCUMENTATION = r'''
+module: podman_logout
+author:
+ - "Clemens Lange (@clelange)"
+short_description: Log out of a container registry using podman
+notes: []
+description:
+ - Log out of a container registry server using the podman logout command
+ by deleting the cached credentials stored in the `auth.json` file.
+ If the registry is not specified, the first registry under
+ `[registries.search]` from `registries.conf `will be used. The path of
+ the authentication file can be overridden by the user by setting the
+ `authfile` flag. The default path used is
+ `${XDG_RUNTIME_DIR}/containers/auth.json`.
+ All the cached credentials can be removed by setting the `all` flag.
+ Warning - podman will use credentials in `${HOME}/.docker/config.json`
+ to authenticate in case they are not found in the default `authfile`.
+ However, the logout command will only removed credentials in the
+ `authfile` specified.
+requirements:
+ - "Podman installed on host"
+options:
+ registry:
+ description:
+ - Registry server. If the registry is not specified,
+ the first registry under `[registries.search]` from
+ `registries.conf` will be used.
+ type: str
+ authfile:
+ description:
+ - Path of the authentication file. Default is
+ ``${XDG_RUNTIME_DIR}/containers/auth.json``
+ 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: path
+ all:
+ description:
+ - Remove the cached credentials for all registries in the auth file.
+ type: bool
+ ignore_docker_credentials:
+ description:
+ - Credentials created using other tools such as `docker login` are not
+ removed unless the corresponding `authfile` is explicitly specified.
+ Since podman also uses existing credentials in these files by default
+ (for docker e.g. `${HOME}/.docker/config.json`), module execution will
+ fail if a docker login exists for the registry specified in any
+ `authfile` is used by podman. This can be ignored by setting
+ `ignore_docker_credentials` to `yes` - the credentials will be kept and
+ `changed` will be false.
+ This option cannot be used together with `all` since in this case
+ podman will not check for existing `authfiles` created by other tools.
+ type: bool
+ 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 = r"""
+- name: Log out of default registry
+ podman_logout:
+
+- name: Log out of quay.io
+ podman_logout:
+ registry: quay.io
+
+- name: Log out of all registries in auth file
+ podman_logout:
+ all: yes
+
+- name: Log out of all registries in specified auth file
+ podman_logout:
+ authfile: $HOME/.docker/config.json
+ all: yes
+"""
+# noqa: F402
+
+import json # noqa: F402
+from ansible.module_utils.basic import AnsibleModule
+
+
+def logout(module, executable, registry, authfile, all_registries, ignore_docker_credentials):
+ command = [executable, 'logout']
+ changed = False
+ if authfile:
+ command.extend(['--authfile', authfile])
+ if registry:
+ command.append(registry)
+ if all_registries:
+ command.append("--all")
+ rc, out, err = module.run_command(command)
+ if rc != 0:
+ if 'Error: Not logged into' not in err:
+ module.fail_json(msg="Unable to gather info for %s: %s" % (registry, err))
+ else:
+ # If the command is successful, we managed to log out
+ # Mind: This also applied if --all flag is used, while in this case
+ # there is no check whether one has been logged into any registry
+ changed = True
+ if 'Existing credentials were established via' in out:
+ # The command will return successfully but not log out the user if the
+ # credentials were initially created using docker. Catch this behaviour:
+ if not ignore_docker_credentials:
+ module.fail_json(msg="Unable to log out of %s: %s" % (registry, out))
+ else:
+ changed = False
+ return changed, out, err
+
+
+def main():
+ module = AnsibleModule(
+ argument_spec=dict(
+ executable=dict(type='str', default='podman'),
+ registry=dict(type='str'),
+ authfile=dict(type='path'),
+ all=dict(type='bool'),
+ ignore_docker_credentials=dict(type='bool'),
+ ),
+ supports_check_mode=True,
+ mutually_exclusive=(
+ ['registry', 'all'],
+ ['ignore_docker_credentials', 'all'],
+ ),
+ )
+
+ registry = module.params['registry']
+ authfile = module.params['authfile']
+ all_registries = module.params['all']
+ ignore_docker_credentials = module.params['ignore_docker_credentials']
+ executable = module.get_bin_path(module.params['executable'], required=True)
+
+ changed, out, err = logout(module, executable, registry, authfile,
+ all_registries, ignore_docker_credentials)
+
+ results = {
+ "changed": changed,
+ "stdout": out,
+ "stderr": err,
+ }
+
+ module.exit_json(**results)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_network.py b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_network.py
new file mode 100644
index 00000000..590b4690
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_network.py
@@ -0,0 +1,614 @@
+#!/usr/bin/python
+# Copyright (c) 2020 Red Hat
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+DOCUMENTATION = r"""
+module: podman_network
+author:
+ - "Sagi Shnaidman (@sshnaidm)"
+version_added: '1.0.0'
+short_description: Manage podman networks
+notes: []
+description:
+ - Manage podman networks with podman network command.
+requirements:
+ - podman
+options:
+ name:
+ description:
+ - Name of the network
+ type: str
+ required: True
+ 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
+ disable_dns:
+ description:
+ - disable dns plugin (default "false")
+ type: bool
+ driver:
+ description:
+ - Driver to manage the network (default "bridge")
+ type: str
+ gateway:
+ description:
+ - IPv4 or IPv6 gateway for the subnet
+ type: str
+ internal:
+ description:
+ - Restrict external access from this network (default "false")
+ type: bool
+ ip_range:
+ description:
+ - Allocate container IP from range
+ type: str
+ ipv6:
+ description:
+ - Enable IPv6 (Dual Stack) networking. You must pass a IPv6 subnet.
+ The subnet option must be used with the ipv6 option.
+ type: bool
+ subnet:
+ description:
+ - Subnet in CIDR format
+ type: str
+ macvlan:
+ description:
+ - Create a Macvlan connection based on this device
+ type: str
+ opt:
+ description:
+ - Add network options. Currently 'vlan' and 'mtu' are supported.
+ type: dict
+ suboptions:
+ mtu:
+ description:
+ - MTU size for bridge network interface.
+ type: int
+ required: false
+ vlan:
+ description:
+ - VLAN tag for bridge which enables vlan_filtering.
+ type: int
+ required: false
+ debug:
+ description:
+ - Return additional information which can be helpful for investigations.
+ type: bool
+ default: False
+ state:
+ description:
+ - State of network, default 'present'
+ type: str
+ default: present
+ choices:
+ - present
+ - absent
+ recreate:
+ description:
+ - Recreate network even if exists.
+ type: bool
+ default: false
+"""
+
+EXAMPLES = r"""
+- name: Create a podman network
+ containers.podman.podman_network:
+ name: podman_network
+ become: true
+
+- name: Create internal podman network
+ containers.podman.podman_network:
+ name: podman_internal
+ internal: true
+ ip_range: 192.168.22.128/25
+ subnet: 192.168.22.0/24
+ gateway: 192.168.22.1
+ become: true
+"""
+
+RETURN = r"""
+network:
+ description: Facts from created or updated networks
+ returned: always
+ type: list
+ sample: [
+ {
+ "cniVersion": "0.4.0",
+ "name": "podman",
+ "plugins": [
+ {
+ "bridge": "cni-podman0",
+ "ipMasq": true,
+ "ipam": {
+ "ranges": [
+ [
+ {
+ "gateway": "10.88.0.1",
+ "subnet": "10.88.0.0/16"
+ }
+ ]
+ ],
+ "routes": [
+ {
+ "dst": "0.0.0.0/0"
+ }
+ ],
+ "type": "host-local"
+ },
+ "isGateway": true,
+ "type": "bridge"
+ },
+ {
+ "capabilities": {
+ "portMappings": true
+ },
+ "type": "portmap"
+ },
+ {
+ "backend": "iptables",
+ "type": "firewall"
+ }
+ ]
+ }
+ ]
+"""
+
+import json # noqa: F402
+from distutils.version import LooseVersion # noqa: F402
+import os # noqa: F402
+
+from ansible.module_utils.basic import AnsibleModule # noqa: F402
+from ansible.module_utils._text import to_bytes, to_native # noqa: F402
+
+from ansible_collections.containers.podman.plugins.module_utils.podman.common import lower_keys
+
+
+class PodmanNetworkModuleParams:
+ """Creates list of arguments for podman CLI command.
+
+ Arguments:
+ action {str} -- action type from 'create', 'delete'
+ params {dict} -- dictionary of module parameters
+
+ """
+
+ 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.
+
+ Returns:
+ list -- list of byte strings for Popen command
+ """
+ if self.action in ['delete']:
+ return self._simple_action()
+ if self.action in ['create']:
+ return self._create_action()
+
+ def _simple_action(self):
+ if self.action == 'delete':
+ cmd = ['rm', '-f', self.params['name']]
+ return [to_bytes(i, errors='surrogate_or_strict') for i in cmd]
+
+ def _create_action(self):
+ cmd = [self.action, self.params['name']]
+ all_param_methods = [func for func in dir(self)
+ 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])
+ if func_name in all_param_methods:
+ cmd = getattr(self, func_name)(cmd)
+ return [to_bytes(i, errors='surrogate_or_strict') for i in cmd]
+
+ 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_gateway(self, c):
+ return c + ['--gateway', self.params['gateway']]
+
+ def addparam_driver(self, c):
+ return c + ['--driver', self.params['driver']]
+
+ def addparam_subnet(self, c):
+ return c + ['--subnet', self.params['subnet']]
+
+ def addparam_ip_range(self, c):
+ return c + ['--ip-range', self.params['ip_range']]
+
+ def addparam_ipv6(self, c):
+ return c + ['--ipv6']
+
+ def addparam_macvlan(self, c):
+ return c + ['--macvlan', self.params['macvlan']]
+
+ def addparam_internal(self, c):
+ return c + ['--internal=%s' % self.params['internal']]
+
+ def addparam_opt(self, c):
+ for opt in self.params['opt'].items():
+ c += ['--opt',
+ b"=".join([to_bytes(k, errors='surrogate_or_strict')
+ for k in opt])]
+ return c
+
+ def addparam_disable_dns(self, c):
+ return c + ['--disable-dns=%s' % self.params['disable_dns']]
+
+
+class PodmanNetworkDefaults:
+ def __init__(self, module, podman_version):
+ self.module = module
+ self.version = podman_version
+ self.defaults = {
+ 'driver': 'bridge',
+ 'disable_dns': False,
+ 'internal': False,
+ }
+
+ def default_dict(self):
+ # make here any changes to self.defaults related to podman version
+ return self.defaults
+
+
+class PodmanNetworkDiff:
+ def __init__(self, module, info, podman_version):
+ self.module = module
+ self.version = podman_version
+ self.default_dict = None
+ self.info = lower_keys(info)
+ self.params = self.defaultize()
+ self.diff = {'before': {}, 'after': {}}
+ self.non_idempotent = {}
+
+ def defaultize(self):
+ params_with_defaults = {}
+ self.default_dict = PodmanNetworkDefaults(
+ 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_disable_dns(self):
+ dns_installed = False
+ for f in [
+ '/usr/libexec/cni/dnsname',
+ '/usr/lib/cni/dnsname',
+ '/opt/cni/bin/dnsname',
+ '/opt/bridge/bin/dnsname'
+ ]:
+ if os.path.exists(f):
+ dns_installed = True
+ before = not bool(
+ [k for k in self.info['plugins'] if 'domainname' in k])
+ after = self.params['disable_dns']
+ # If dnsname plugin is not installed, default is disable_dns=True
+ if not dns_installed and self.module.params['disable_dns'] is None:
+ after = True
+ return self._diff_update_and_compare('disable_dns', before, after)
+
+ def diffparam_driver(self):
+ # Currently only bridge is supported
+ before = after = 'bridge'
+ return self._diff_update_and_compare('driver', before, after)
+
+ def diffparam_gateway(self):
+ try:
+ before = self.info['plugins'][0]['ipam']['ranges'][0][0]['gateway']
+ except (IndexError, KeyError):
+ before = ''
+ after = before
+ if self.params['gateway'] is not None:
+ after = self.params['gateway']
+ return self._diff_update_and_compare('gateway', before, after)
+
+ def diffparam_internal(self):
+ try:
+ before = not self.info['plugins'][0]['isgateway']
+ except (IndexError, KeyError):
+ before = False
+ after = self.params['internal']
+ return self._diff_update_and_compare('internal', before, after)
+
+ def diffparam_ip_range(self):
+ # TODO(sshnaidm): implement IP to CIDR convert and vice versa
+ before = after = ''
+ return self._diff_update_and_compare('ip_range', before, after)
+
+ def diffparam_subnet(self):
+ try:
+ before = self.info['plugins'][0]['ipam']['ranges'][0][0]['subnet']
+ except (IndexError, KeyError):
+ before = ''
+ after = before
+ if self.params['subnet'] is not None:
+ after = self.params['subnet']
+ return self._diff_update_and_compare('subnet', before, after)
+
+ def diffparam_macvlan(self):
+ before = after = ''
+ return self._diff_update_and_compare('macvlan', before, after)
+
+ def diffparam_opt(self):
+ vlan_before = self.info['plugins'][0].get('vlan')
+ vlan_after = self.params['opt'].get('vlan') if self.params['opt'] else None
+ if vlan_before or vlan_after:
+ before, after = {'vlan': vlan_before}, {'vlan': vlan_after}
+ else:
+ before, after = {}, {}
+ mtu_before = self.info['plugins'][0].get('mtu')
+ mtu_after = self.params['opt'].get('mtu') if self.params['opt'] else None
+ if mtu_before or mtu_after:
+ before.update({'mtu': mtu_before})
+ after.update({'mtu': mtu_after})
+ return self._diff_update_and_compare('opt', 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
+ 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
+
+
+class PodmanNetwork:
+ """Perform network tasks.
+
+ Manages podman network, inspects it and checks its current state
+ """
+
+ def __init__(self, module, name):
+ """Initialize PodmanNetwork class.
+
+ Arguments:
+ module {obj} -- ansible module object
+ name {str} -- name of network
+ """
+
+ super(PodmanNetwork, self).__init__()
+ self.module = module
+ 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):
+ """Check if network exists."""
+ return bool(self.info != {})
+
+ @property
+ def different(self):
+ """Check if network is different."""
+ diffcheck = PodmanNetworkDiff(
+ 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
+
+ def get_info(self):
+ """Inspect network and gather info about it."""
+ # pylint: disable=unused-variable
+ rc, out, err = self.module.run_command(
+ [self.module.params['executable'], b'network', b'inspect', self.name])
+ return json.loads(out)[0] if rc == 0 else {}
+
+ def _get_podman_version(self):
+ # pylint: disable=unused-variable
+ 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 network.
+
+ Arguments:
+ action {str} -- action to perform - create, stop, delete
+ """
+ b_command = PodmanNetworkModuleParams(action,
+ self.module.params,
+ self.version,
+ self.module,
+ ).construct_command_from_params()
+ full_cmd = " ".join([self.module.params['executable'], 'network']
+ + [to_native(i) for i in b_command])
+ self.module.log("PODMAN-NETWORK-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'network'] + b_command,
+ expand_user_and_vars=False)
+ self.stdout = out
+ self.stderr = err
+ if rc != 0:
+ self.module.fail_json(
+ msg="Can't %s network %s" % (action, self.name),
+ stdout=out, stderr=err)
+
+ def delete(self):
+ """Delete the network."""
+ self._perform_action('delete')
+
+ def create(self):
+ """Create the network."""
+ self._perform_action('create')
+
+ def recreate(self):
+ """Recreate the network."""
+ self.delete()
+ self.create()
+
+
+class PodmanNetworkManager:
+ """Module manager class.
+
+ Defines according to parameters what actions should be applied to network
+ """
+
+ def __init__(self, module):
+ """Initialize PodmanManager class.
+
+ Arguments:
+ module {obj} -- ansible module object
+ """
+
+ super(PodmanNetworkManager, self).__init__()
+
+ self.module = module
+ self.results = {
+ 'changed': False,
+ 'actions': [],
+ 'network': {},
+ }
+ self.name = self.module.params['name']
+ self.executable = \
+ self.module.get_bin_path(self.module.params['executable'],
+ required=True)
+ self.state = self.module.params['state']
+ self.recreate = self.module.params['recreate']
+ self.network = PodmanNetwork(self.module, self.name)
+
+ def update_network_result(self, changed=True):
+ """Inspect the current network, update results with last info, exit.
+
+ Keyword Arguments:
+ changed {bool} -- whether any action was performed
+ (default: {True})
+ """
+ facts = self.network.get_info() if changed else self.network.info
+ out, err = self.network.stdout, self.network.stderr
+ self.results.update({'changed': changed, 'network': facts,
+ 'podman_actions': self.network.actions},
+ stdout=out, stderr=err)
+ if self.network.diff:
+ self.results.update({'diff': self.network.diff})
+ if self.module.params['debug']:
+ self.results.update({'podman_version': self.network.version})
+ self.module.exit_json(**self.results)
+
+ def execute(self):
+ """Execute the desired action according to map of actions & states."""
+ states_map = {
+ 'present': self.make_present,
+ 'absent': self.make_absent,
+ }
+ process_action = states_map[self.state]
+ process_action()
+ self.module.fail_json(msg="Unexpected logic error happened, "
+ "please contact maintainers ASAP!")
+
+ def make_present(self):
+ """Run actions if desired state is 'started'."""
+ if not self.network.exists:
+ self.network.create()
+ self.results['actions'].append('created %s' % self.network.name)
+ self.update_network_result()
+ elif self.recreate or self.network.different:
+ self.network.recreate()
+ self.results['actions'].append('recreated %s' %
+ self.network.name)
+ self.update_network_result()
+ else:
+ self.update_network_result(changed=False)
+
+ def make_absent(self):
+ """Run actions if desired state is 'absent'."""
+ if not self.network.exists:
+ self.results.update({'changed': False})
+ elif self.network.exists:
+ self.network.delete()
+ self.results['actions'].append('deleted %s' % self.network.name)
+ self.results.update({'changed': True})
+ self.results.update({'network': {},
+ 'podman_actions': self.network.actions})
+ self.module.exit_json(**self.results)
+
+
+def main():
+ module = AnsibleModule(
+ argument_spec=dict(
+ state=dict(type='str', default="present",
+ choices=['present', 'absent']),
+ name=dict(type='str', required=True),
+ disable_dns=dict(type='bool', required=False),
+ driver=dict(type='str', required=False),
+ gateway=dict(type='str', required=False),
+ internal=dict(type='bool', required=False),
+ ip_range=dict(type='str', required=False),
+ ipv6=dict(type='bool', required=False),
+ subnet=dict(type='str', required=False),
+ macvlan=dict(type='str', required=False),
+ opt=dict(type='dict', required=False,
+ options=dict(
+ mtu=dict(type='int', required=False),
+ vlan=dict(type='int', required=False))),
+ executable=dict(type='str', required=False, default='podman'),
+ debug=dict(type='bool', default=False),
+ recreate=dict(type='bool', default=False),
+ ),
+ required_by=dict( # for IP range and GW to set 'subnet' is required
+ ip_range=('subnet'),
+ gateway=('subnet'),
+ ))
+
+ PodmanNetworkManager(module).execute()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_network_info.py b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_network_info.py
new file mode 100644
index 00000000..a9e18cd4
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_network_info.py
@@ -0,0 +1,138 @@
+#!/usr/bin/python
+# Copyright (c) 2020 Red Hat
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+DOCUMENTATION = r"""
+module: podman_network_info
+author:
+ - "Sagi Shnaidman (@sshnaidm)"
+version_added: '1.0.0'
+short_description: Gather info about podman networks
+notes: []
+description:
+ - Gather info about podman networks with podman inspect command.
+requirements:
+ - "Podman installed on host"
+options:
+ name:
+ description:
+ - Name of the network
+ type: 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 = r"""
+- name: Gather info about all present networks
+ containers.podman.podman_network_info:
+
+- name: Gather info about specific network
+ containers.podman.podman_network_info:
+ name: podman
+"""
+
+RETURN = r"""
+networks:
+ description: Facts from all or specified networks
+ returned: always
+ type: list
+ sample: [
+ {
+ "cniVersion": "0.4.0",
+ "name": "podman",
+ "plugins": [
+ {
+ "bridge": "cni-podman0",
+ "ipMasq": true,
+ "ipam": {
+ "ranges": [
+ [
+ {
+ "gateway": "10.88.0.1",
+ "subnet": "10.88.0.0/16"
+ }
+ ]
+ ],
+ "routes": [
+ {
+ "dst": "0.0.0.0/0"
+ }
+ ],
+ "type": "host-local"
+ },
+ "isGateway": true,
+ "type": "bridge"
+ },
+ {
+ "capabilities": {
+ "portMappings": true
+ },
+ "type": "portmap"
+ },
+ {
+ "backend": "iptables",
+ "type": "firewall"
+ }
+ ]
+ }
+ ]
+"""
+
+import json
+from ansible.module_utils.basic import AnsibleModule
+
+
+def get_network_info(module, executable, name):
+ command = [executable, 'network', 'inspect']
+ if not name:
+ all_names = [executable, 'network', 'ls', '-q']
+ rc, out, err = module.run_command(all_names)
+ if rc != 0:
+ module.fail_json(msg="Unable to get list of networks: %s" % err)
+ name = out.split()
+ if not name:
+ return [], out, err
+ command += name
+ else:
+ command.append(name)
+ rc, out, err = module.run_command(command)
+ if rc != 0 or 'unable to find network configuration' in err:
+ module.fail_json(msg="Unable to gather info for %s: %s" % (name, 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=dict(
+ executable=dict(type='str', default='podman'),
+ name=dict(type='str')
+ ),
+ supports_check_mode=True,
+ )
+
+ name = module.params['name']
+ executable = module.get_bin_path(module.params['executable'], required=True)
+
+ inspect_results, out, err = get_network_info(module, executable, name)
+
+ results = {
+ "changed": False,
+ "networks": inspect_results,
+ "stderr": err
+ }
+
+ module.exit_json(**results)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_pod.py b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_pod.py
new file mode 100644
index 00000000..01dbc627
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_pod.py
@@ -0,0 +1,232 @@
+#!/usr/bin/python
+# Copyright (c) 2020 Red Hat
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+# flake8: noqa: E501
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+DOCUMENTATION = '''
+---
+module: podman_pod
+short_description: Manage Podman pods
+author:
+ - "Sagi Shnaidman (@sshnaidm)"
+version_added: '1.0.0'
+description:
+ - Manage podman pods.
+options:
+ state:
+ description:
+ - This variable is set for state
+ type: str
+ default: created
+ choices:
+ - created
+ - killed
+ - restarted
+ - absent
+ - started
+ - stopped
+ - paused
+ - unpaused
+ recreate:
+ description:
+ - Use with present and started states to force the re-creation of an
+ existing pod.
+ type: bool
+ default: False
+ add_host:
+ description:
+ - Add a host to the /etc/hosts file shared between all containers in the pod.
+ type: list
+ elements: str
+ required: false
+ cgroup_parent:
+ description:
+ - Path to cgroups under which the cgroup for the pod will be created. If the path
+ is not absolute, he 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: str
+ required: false
+ dns:
+ description:
+ - Set custom DNS servers in the /etc/resolv.conf file that will be shared between
+ all containers in the pod. A special option, "none" is allowed which disables
+ creation of /etc/resolv.conf for the pod.
+ type: list
+ elements: str
+ required: false
+ dns_opt:
+ description:
+ - Set custom DNS options in the /etc/resolv.conf file that will be shared between
+ all containers in the pod.
+ type: list
+ elements: str
+ required: false
+ dns_search:
+ description:
+ - Set custom DNS search domains in the /etc/resolv.conf file that will be shared
+ between all containers in the pod.
+ type: list
+ elements: str
+ required: false
+ hostname:
+ description:
+ - Set a hostname to the pod
+ type: str
+ required: false
+ infra:
+ description:
+ - Create an infra container and associate it with the pod. An infra container is
+ a lightweight container used to coordinate the shared kernel namespace of a pod.
+ Default is true.
+ type: bool
+ required: false
+ infra_conmon_pidfile:
+ description:
+ - Write the pid of the infra container's conmon process to a file. As conmon runs
+ in a separate process than Podman, this is necessary when using systemd to manage
+ Podman containers and pods.
+ type: str
+ required: false
+ infra_command:
+ description:
+ - The command that will be run to start the infra container. Default is "/pause".
+ type: str
+ required: false
+ infra_image:
+ description:
+ - The image that will be created for the infra container. Default is "k8s.gcr.io/pause:3.1".
+ type: str
+ required: false
+ ip:
+ description:
+ - Set a static IP for the pod's shared network.
+ type: str
+ required: false
+ label:
+ description:
+ - Add metadata to a pod, pass dictionary of label keys and values.
+ type: dict
+ required: false
+ label_file:
+ description:
+ - Read in a line delimited file of labels.
+ type: str
+ required: false
+ mac_address:
+ description:
+ - Set a static MAC address for the pod's shared network.
+ type: str
+ required: false
+ name:
+ description:
+ - Assign a name to the pod.
+ type: str
+ required: true
+ network:
+ description:
+ - Set network mode for the pod. Supported values are bridge (the default), host
+ (do not create a network namespace, all containers in the pod will use the host's
+ network), or a comma-separated list of the names of CNI networks the pod should
+ join.
+ type: str
+ required: false
+ no_hosts:
+ description:
+ - Disable creation of /etc/hosts for the pod.
+ type: bool
+ required: false
+ pod_id_file:
+ description:
+ - Write the pod ID to the file.
+ type: str
+ required: false
+ publish:
+ description:
+ - Publish a port or range of ports from the pod to the host.
+ type: list
+ elements: str
+ required: false
+ aliases:
+ - ports
+ share:
+ description:
+ - A comma delimited list of kernel namespaces to share. If none or "" is specified,
+ no namespaces will be shared. The namespaces to choose from are ipc, net, pid,
+ user, uts.
+ type: str
+ required: false
+ 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
+ debug:
+ description:
+ - Return additional information which can be helpful for investigations.
+ type: bool
+ default: False
+
+requirements:
+ - "podman"
+
+'''
+
+RETURN = '''
+pod:
+ description: Pod inspection results for the given pod
+ built.
+ returned: always
+ type: dict
+ sample:
+ Config:
+ cgroupParent: /libpod_parent
+ created: '2020-06-14T15:16:12.230818767+03:00'
+ hostname: newpod
+ id: a5a5c6cdf8c72272fc5c33f787e8d7501e2fa0c1e92b2b602860defdafeeec58
+ infraConfig:
+ infraPortBindings: null
+ makeInfraContainer: true
+ labels: {}
+ lockID: 515
+ name: newpod
+ sharesCgroup: true
+ sharesIpc: true
+ sharesNet: true
+ sharesUts: true
+ Containers:
+ - id: dc70a947c7ae15198ec38b3c817587584085dee3919cbeb9969e3ab77ba10fd2
+ state: configured
+ State:
+ cgroupPath: /libpod_parent/a5a5c6cdf8c72272fc5c33f787e8d7501e2fa0c1e92b2b602860defdafeeec58
+ infraContainerID: dc70a947c7ae15198ec38b3c817587584085dee3919cbeb9969e3ab77ba10fd2
+ status: Created
+
+'''
+
+EXAMPLES = '''
+# What modules does for example
+- podman_pod:
+ name: pod1
+ state: started
+ ports:
+ - 4444:5555
+'''
+from ansible.module_utils.basic import AnsibleModule # noqa: F402
+from ..module_utils.podman.podman_pod_lib import PodmanPodManager # noqa: F402
+from ..module_utils.podman.podman_pod_lib import ARGUMENTS_SPEC_POD # noqa: F402
+
+
+def main():
+ module = AnsibleModule(
+ argument_spec=ARGUMENTS_SPEC_POD
+ )
+ results = PodmanPodManager(module, module.params).execute()
+ module.exit_json(**results)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_pod_info.py b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_pod_info.py
new file mode 100644
index 00000000..c02e503c
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_pod_info.py
@@ -0,0 +1,145 @@
+#!/usr/bin/python
+# Copyright (c) 2020 Red Hat
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+DOCUMENTATION = r"""
+module: podman_pod_info
+author:
+ - "Sagi Shnaidman (@sshnaidm)"
+version_added: '1.0.0'
+short_description: Gather info about podman pods
+notes: []
+description:
+ - Gather info about podman pods with podman inspect command.
+requirements:
+ - "Podman installed on host"
+options:
+ name:
+ description:
+ - Name of the pod
+ type: 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 = r"""
+- name: Gather info about all present pods
+ containers.podman.podman_pod_info:
+
+- name: Gather info about specific pods
+ containers.podman.podman_pod_info:
+ name: special_pod
+"""
+
+RETURN = r"""
+pods:
+ description: Facts from all or specified pods
+ returned: always
+ type: list
+ sample: [
+ {
+ "Config": {
+ "id": "d9cb6dbb0....",
+ "name": "pod1",
+ "hostname": "pod1host",
+ "labels": {
+ },
+ "cgroupParent": "/libpod_parent",
+ "sharesCgroup": true,
+ "sharesIpc": true,
+ "sharesNet": true,
+ "sharesUts": true,
+ "infraConfig": {
+ "makeInfraContainer": true,
+ "infraPortBindings": [
+ {
+ "hostPort": 7777,
+ "containerPort": 7111,
+ "protocol": "tcp",
+ "hostIP": ""
+ }
+ ]
+ },
+ "created": "2020-07-13T20:29:12.572282186+03:00",
+ "lockID": 682
+ },
+ "State": {
+ "cgroupPath": "/libpod_parent/d9cb6dbb0....",
+ "infraContainerID": "ad46737bf....",
+ "status": "Created"
+ },
+ "Containers": [
+ {
+ "id": "ad46737bf....",
+ "state": "configured"
+ }
+ ]
+ }
+ ]
+"""
+
+import json
+from ansible.module_utils.basic import AnsibleModule
+
+
+def get_pod_info(module, executable, name):
+ command = [executable, 'pod', 'inspect']
+ pods = [name]
+ result = []
+ errs = []
+ rcs = []
+ if not name:
+ all_names = [executable, 'pod', 'ls', '-q']
+ rc, out, err = module.run_command(all_names)
+ if rc != 0:
+ module.fail_json(msg="Unable to get list of pods: %s" % err)
+ name = out.split()
+ if not name:
+ return [], out, err
+ pods = name
+ for pod in pods:
+ rc, out, err = module.run_command(command + [pod])
+ errs.append(err.strip())
+ rcs += [rc]
+ if not out or json.loads(out) is None:
+ continue
+ result.append(json.loads(out))
+ return result, errs, rcs
+
+
+def main():
+ module = AnsibleModule(
+ argument_spec=dict(
+ executable=dict(type='str', default='podman'),
+ name=dict(type='str')
+ ),
+ supports_check_mode=True,
+ )
+
+ name = module.params['name']
+ executable = module.get_bin_path(module.params['executable'], required=True)
+
+ inspect_results, errs, rcs = get_pod_info(module, executable, name)
+
+ if len(rcs) > 1 and 0 not in rcs:
+ module.fail_json(msg="Failed to inspect pods", stderr="\n".join(errs))
+
+ results = {
+ "changed": False,
+ "pods": inspect_results,
+ "stderr": "\n".join(errs),
+ }
+
+ module.exit_json(**results)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_volume.py b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_volume.py
new file mode 100644
index 00000000..d74e39d3
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_volume.py
@@ -0,0 +1,477 @@
+#!/usr/bin/python
+# Copyright (c) 2020 Red Hat
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+# flake8: noqa: E501
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+DOCUMENTATION = '''
+---
+module: podman_volume
+short_description: Manage Podman volumes
+author:
+ - "Sagi Shnaidman (@sshnaidm)"
+version_added: '1.1.0'
+description:
+ - Manage Podman volumes
+options:
+ state:
+ description:
+ - State of volume, default 'present'
+ type: str
+ default: present
+ choices:
+ - present
+ - absent
+ recreate:
+ description:
+ - Recreate volume even if exists.
+ type: bool
+ default: false
+ name:
+ description:
+ - Name of volume.
+ type: str
+ required: true
+ label:
+ description:
+ - Add metadata to a pod volume (e.g., label com.example.key=value).
+ type: dict
+ required: false
+ driver:
+ description:
+ - Specify volume driver name (default local).
+ type: str
+ required: false
+ options:
+ description:
+ - Set driver specific options. For example 'device=tpmfs', 'type=tmpfs'.
+ type: list
+ elements: str
+ required: false
+ 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
+ debug:
+ description:
+ - Return additional information which can be helpful for investigations.
+ type: bool
+ default: False
+
+requirements:
+ - "podman"
+
+'''
+
+RETURN = '''
+volume:
+ description: Volume inspection results if exists.
+ returned: always
+ type: dict
+ sample:
+ CreatedAt: '2020-06-05T16:38:55.277628769+03:00'
+ Driver: local
+ Labels:
+ key.com: value
+ key.org: value2
+ Mountpoint: /home/user/.local/share/containers/storage/volumes/test/_data
+ Name: test
+ Options: {}
+ Scope: local
+
+'''
+
+EXAMPLES = '''
+# What modules does for example
+- podman_volume:
+ state: present
+ name: volume1
+ label:
+ key: value
+ key2: value2
+ options:
+ - "device=/dev/loop1"
+ - "type=ext4"
+'''
+# noqa: F402
+import json # noqa: F402
+from distutils.version import LooseVersion # noqa: F402
+
+from ansible.module_utils.basic import AnsibleModule # noqa: F402
+from ansible.module_utils._text import to_bytes, to_native # noqa: F402
+
+from ansible_collections.containers.podman.plugins.module_utils.podman.common import lower_keys
+
+
+class PodmanVolumeModuleParams:
+ """Creates list of arguments for podman CLI command.
+
+ Arguments:
+ action {str} -- action type from 'create', 'delete'
+ params {dict} -- dictionary of module parameters
+
+ """
+
+ 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.
+
+ Returns:
+ list -- list of byte strings for Popen command
+ """
+ if self.action in ['delete']:
+ return self._simple_action()
+ if self.action in ['create']:
+ return self._create_action()
+
+ def _simple_action(self):
+ if self.action == 'delete':
+ cmd = ['rm', '-f', self.params['name']]
+ return [to_bytes(i, errors='surrogate_or_strict') for i in cmd]
+
+ def _create_action(self):
+ cmd = [self.action, self.params['name']]
+ all_param_methods = [func for func in dir(self)
+ 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])
+ if func_name in all_param_methods:
+ cmd = getattr(self, func_name)(cmd)
+ return [to_bytes(i, errors='surrogate_or_strict') for i in cmd]
+
+ 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_label(self, c):
+ for label in self.params['label'].items():
+ c += ['--label', b'='.join(
+ [to_bytes(l, errors='surrogate_or_strict') for l in label])]
+ return c
+
+ def addparam_driver(self, c):
+ return c + ['--driver', self.params['driver']]
+
+ def addparam_options(self, c):
+ for opt in self.params['options']:
+ c += ['--opt', opt]
+ return c
+
+
+class PodmanVolumeDefaults:
+ def __init__(self, module, podman_version):
+ self.module = module
+ self.version = podman_version
+ self.defaults = {
+ 'driver': 'local',
+ 'label': {},
+ 'options': {}
+ }
+
+ def default_dict(self):
+ # make here any changes to self.defaults related to podman version
+ return self.defaults
+
+
+class PodmanVolumeDiff:
+ def __init__(self, module, info, podman_version):
+ self.module = module
+ self.version = podman_version
+ self.default_dict = None
+ self.info = lower_keys(info)
+ self.params = self.defaultize()
+ self.diff = {'before': {}, 'after': {}}
+ self.non_idempotent = {}
+
+ def defaultize(self):
+ params_with_defaults = {}
+ self.default_dict = PodmanVolumeDefaults(
+ 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_label(self):
+ before = self.info['labels'] if 'labels' in self.info else {}
+ after = self.params['label']
+ return self._diff_update_and_compare('label', before, after)
+
+ def diffparam_driver(self):
+ before = self.info['driver']
+ after = self.params['driver']
+ return self._diff_update_and_compare('driver', before, after)
+
+ def diffparam_options(self):
+ before = self.info['options'] if 'options' in self.info else {}
+ before = ["=".join((k, v)) for k, v in before.items()]
+ after = self.params['options']
+ # Gor UID, GID
+ ids = []
+ if self.info['uid']:
+ before += ['uid=%s' % str(self.info['uid'])]
+ if self.info['gid']:
+ before += ['gid=%s' % str(self.info['gid'])]
+ if self.params['options']:
+ for opt in self.params['options']:
+ if 'uid=' in opt or 'gid=' in opt:
+ ids += opt.split("o=")[1].split(",")
+ after = [i for i in after if 'gid' not in i and 'uid' not in i]
+ after += ids
+ before, after = sorted(list(set(before))), sorted(list(set(after)))
+ return self._diff_update_and_compare('options', 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
+
+
+class PodmanVolume:
+ """Perform volume tasks.
+
+ Manages podman volume, inspects it and checks its current state
+ """
+
+ def __init__(self, module, name):
+ """Initialize PodmanVolume class.
+
+ Arguments:
+ module {obj} -- ansible module object
+ name {str} -- name of volume
+ """
+
+ super(PodmanVolume, self).__init__()
+ self.module = module
+ 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):
+ """Check if volume exists."""
+ return bool(self.info != {})
+
+ @property
+ def different(self):
+ """Check if volume is different."""
+ diffcheck = PodmanVolumeDiff(
+ 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
+
+ def get_info(self):
+ """Inspect volume and gather info about it."""
+ # pylint: disable=unused-variable
+ rc, out, err = self.module.run_command(
+ [self.module.params['executable'], b'volume', b'inspect', self.name])
+ return json.loads(out)[0] if rc == 0 else {}
+
+ def _get_podman_version(self):
+ # pylint: disable=unused-variable
+ 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 volume.
+
+ Arguments:
+ action {str} -- action to perform - create, stop, delete
+ """
+ b_command = PodmanVolumeModuleParams(action,
+ self.module.params,
+ self.version,
+ self.module,
+ ).construct_command_from_params()
+ full_cmd = " ".join([self.module.params['executable'], 'volume']
+ + [to_native(i) for i in b_command])
+ self.module.log("PODMAN-VOLUME-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'volume'] + b_command,
+ expand_user_and_vars=False)
+ self.stdout = out
+ self.stderr = err
+ if rc != 0:
+ self.module.fail_json(
+ msg="Can't %s volume %s" % (action, self.name),
+ stdout=out, stderr=err)
+
+ def delete(self):
+ """Delete the volume."""
+ self._perform_action('delete')
+
+ def create(self):
+ """Create the volume."""
+ self._perform_action('create')
+
+ def recreate(self):
+ """Recreate the volume."""
+ self.delete()
+ self.create()
+
+
+class PodmanVolumeManager:
+ """Module manager class.
+
+ Defines according to parameters what actions should be applied to volume
+ """
+
+ def __init__(self, module):
+ """Initialize PodmanManager class.
+
+ Arguments:
+ module {obj} -- ansible module object
+ """
+
+ super(PodmanVolumeManager, self).__init__()
+
+ self.module = module
+ self.results = {
+ 'changed': False,
+ 'actions': [],
+ 'volume': {},
+ }
+ self.name = self.module.params['name']
+ self.executable = \
+ self.module.get_bin_path(self.module.params['executable'],
+ required=True)
+ self.state = self.module.params['state']
+ self.recreate = self.module.params['recreate']
+ self.volume = PodmanVolume(self.module, self.name)
+
+ def update_volume_result(self, changed=True):
+ """Inspect the current volume, update results with last info, exit.
+
+ Keyword Arguments:
+ changed {bool} -- whether any action was performed
+ (default: {True})
+ """
+ facts = self.volume.get_info() if changed else self.volume.info
+ out, err = self.volume.stdout, self.volume.stderr
+ self.results.update({'changed': changed, 'volume': facts,
+ 'podman_actions': self.volume.actions},
+ stdout=out, stderr=err)
+ if self.volume.diff:
+ self.results.update({'diff': self.volume.diff})
+ if self.module.params['debug']:
+ self.results.update({'podman_version': self.volume.version})
+ self.module.exit_json(**self.results)
+
+ def execute(self):
+ """Execute the desired action according to map of actions & states."""
+ states_map = {
+ 'present': self.make_present,
+ 'absent': self.make_absent,
+ }
+ process_action = states_map[self.state]
+ process_action()
+ self.module.fail_json(msg="Unexpected logic error happened, "
+ "please contact maintainers ASAP!")
+
+ def make_present(self):
+ """Run actions if desired state is 'started'."""
+ if not self.volume.exists:
+ self.volume.create()
+ self.results['actions'].append('created %s' % self.volume.name)
+ self.update_volume_result()
+ elif self.recreate or self.volume.different:
+ self.volume.recreate()
+ self.results['actions'].append('recreated %s' %
+ self.volume.name)
+ self.update_volume_result()
+ else:
+ self.update_volume_result(changed=False)
+
+ def make_absent(self):
+ """Run actions if desired state is 'absent'."""
+ if not self.volume.exists:
+ self.results.update({'changed': False})
+ elif self.volume.exists:
+ self.volume.delete()
+ self.results['actions'].append('deleted %s' % self.volume.name)
+ self.results.update({'changed': True})
+ self.results.update({'volume': {},
+ 'podman_actions': self.volume.actions})
+ self.module.exit_json(**self.results)
+
+
+def main():
+ module = AnsibleModule(
+ argument_spec=dict(
+ state=dict(type='str', default="present",
+ choices=['present', 'absent']),
+ name=dict(type='str', required=True),
+ label=dict(type='dict', required=False),
+ driver=dict(type='str', required=False),
+ options=dict(type='list', elements='str', required=False),
+ recreate=dict(type='bool', default=False),
+ executable=dict(type='str', required=False, default='podman'),
+ debug=dict(type='bool', default=False),
+ ))
+
+ PodmanVolumeManager(module).execute()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_volume_info.py b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_volume_info.py
new file mode 100644
index 00000000..97b43b3c
--- /dev/null
+++ b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_volume_info.py
@@ -0,0 +1,100 @@
+#!/usr/bin/python
+# Copyright (c) 2020 Red Hat
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+
+DOCUMENTATION = r'''
+module: podman_volume_info
+author:
+ - "Sagi Shnaidman (@sshnaidm)"
+short_description: Gather info about podman volumes
+notes: []
+description:
+ - Gather info about podman volumes with podman inspect command.
+requirements:
+ - "Podman installed on host"
+options:
+ name:
+ description:
+ - Name of the volume
+ type: 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 = r"""
+- name: Gather info about all present volumes
+ podman_volume_info:
+
+- name: Gather info about specific volume
+ podman_volume_info:
+ name: specific_volume
+"""
+
+RETURN = r"""
+volumes:
+ description: Facts from all or specified volumes
+ returned: always
+ 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']
+ if name:
+ command.append(name)
+ else:
+ command.append("--all")
+ rc, out, err = module.run_command(command)
+ 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=dict(
+ executable=dict(type='str', default='podman'),
+ name=dict(type='str')
+ ),
+ supports_check_mode=True,
+ )
+
+ name = module.params['name']
+ executable = module.get_bin_path(module.params['executable'], required=True)
+
+ inspect_results, out, err = get_volume_info(module, executable, name)
+
+ results = {
+ "changed": False,
+ "volumes": inspect_results,
+ "stderr": err
+ }
+
+ module.exit_json(**results)
+
+
+if __name__ == '__main__':
+ main()