summaryrefslogtreecommitdiffstats
path: root/ansible_collections/containers/podman/plugins/modules
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-26 06:22:20 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-26 06:22:20 +0000
commit18bd2207b6c1977e99a93673a7be099e23f0f547 (patch)
tree40fd9e5913462a88be6ba24be6953383c5b39874 /ansible_collections/containers/podman/plugins/modules
parentReleasing progress-linux version 10.0.1+dfsg-1~progress7.99u1. (diff)
downloadansible-18bd2207b6c1977e99a93673a7be099e23f0f547.tar.xz
ansible-18bd2207b6c1977e99a93673a7be099e23f0f547.zip
Merging upstream version 10.1.0+dfsg.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'ansible_collections/containers/podman/plugins/modules')
-rw-r--r--ansible_collections/containers/podman/plugins/modules/podman_container.py196
-rw-r--r--ansible_collections/containers/podman/plugins/modules/podman_image.py187
-rw-r--r--ansible_collections/containers/podman/plugins/modules/podman_network.py190
-rw-r--r--ansible_collections/containers/podman/plugins/modules/podman_pod.py128
-rw-r--r--ansible_collections/containers/podman/plugins/modules/podman_search.py131
-rw-r--r--ansible_collections/containers/podman/plugins/modules/podman_secret.py84
-rw-r--r--ansible_collections/containers/podman/plugins/modules/podman_volume.py54
7 files changed, 858 insertions, 112 deletions
diff --git a/ansible_collections/containers/podman/plugins/modules/podman_container.py b/ansible_collections/containers/podman/plugins/modules/podman_container.py
index 75349f14e..b06c9ae9e 100644
--- a/ansible_collections/containers/podman/plugins/modules/podman_container.py
+++ b/ansible_collections/containers/podman/plugins/modules/podman_container.py
@@ -79,6 +79,11 @@ options:
- Add an annotation to the container. The format is key value, multiple
times.
type: dict
+ arch:
+ description:
+ - Set the architecture for the container.
+ Override the architecture, defaults to hosts, of the image to be pulled. For example, arm.
+ type: str
attach:
description:
- Attach to STDIN, STDOUT or STDERR. The default in Podman is false.
@@ -125,6 +130,10 @@ options:
the cgroups path of the init process. Cgroups will be created if they
do not already exist.
type: path
+ cgroup_conf:
+ description:
+ - When running on cgroup v2, specify the cgroup file to write to and its value.
+ type: dict
cgroupns:
description:
- Path to cgroups under which the cgroup for the container will be
@@ -137,6 +146,10 @@ options:
The disabled option will force the container to not create CGroups,
and thus conflicts with CGroup options cgroupns and cgroup-parent.
type: str
+ chrootdirs:
+ description:
+ - Path to a directory inside the container that is treated as a chroot directory.
+ type: str
cidfile:
description:
- Write the container ID to the file
@@ -196,6 +209,10 @@ options:
- Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only
effective on NUMA systems.
type: str
+ decryption_key:
+ description:
+ - The "key-passphrase" to be used for decryption of images. Key can point to keys and/or certificates.
+ type: str
delete_depend:
description:
- Remove selected container and recursively remove all containers that depend on it.
@@ -234,6 +251,12 @@ options:
(e.g. device /dev/sdc:/dev/xvdc:rwm)
type: list
elements: str
+ device_cgroup_rule:
+ description:
+ - Add a rule to the cgroup allowed devices list.
+ The rule is expected to be in the format specified in the Linux kernel
+ documentation admin-guide/cgroup-v1/devices.
+ type: str
device_read_bps:
description:
- Limit read rate (bytes per second) from a device
@@ -307,6 +330,10 @@ options:
- Use all current host environment variables in container.
Defaults to false.
type: bool
+ env_merge:
+ description:
+ - Preprocess default environment variables for the containers
+ type: dict
etc_hosts:
description:
- Dict of host-to-IP mappings, where each host name is a key in the
@@ -436,6 +463,10 @@ options:
- Run the container in a new user namespace using the supplied mapping.
type: list
elements: str
+ gpus:
+ description:
+ - GPU devices to add to the container.
+ type: str
group_add:
description:
- Add additional groups to run as
@@ -443,33 +474,70 @@ options:
elements: str
aliases:
- groups
+ group_entry:
+ description:
+ - Customize the entry that is written to the /etc/group file within the container when --user is used.
+ type: str
healthcheck:
description:
- Set or alter a healthcheck command for a container.
type: str
+ aliases:
+ - health_cmd
healthcheck_interval:
description:
- Set an interval for the healthchecks
(a value of disable results in no automatic timer setup)
(default "30s")
type: str
+ aliases:
+ - health_interval
healthcheck_retries:
description:
- The number of retries allowed before a healthcheck is considered to be
unhealthy. The default value is 3.
type: int
+ aliases:
+ - health_retries
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
+ aliases:
+ - health_start_period
+ health_startup_cmd:
+ description:
+ - Set a startup healthcheck command for a container.
+ type: str
+ health_startup_interval:
+ description:
+ - Set an interval for the startup healthcheck.
+ type: str
+ health_startup_retries:
+ description:
+ - The number of attempts allowed before the startup healthcheck restarts the container.
+ If set to 0, the container is never restarted. The default is 0.
+ type: int
+ health_startup_success:
+ description:
+ - The number of successful runs required before the startup healthcheck succeeds
+ and the regular healthcheck begins. A value of 0 means that any success begins the regular healthcheck.
+ The default is 0.
+ type: int
+ health_startup_timeout:
+ description:
+ - The maximum time a startup healthcheck command has to complete before it is marked as failed.
+ 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
+ aliases:
+ - health_timeout
healthcheck_failure_action:
description:
- The action to be taken when the container is considered unhealthy. The action must be one of
@@ -481,6 +549,8 @@ options:
- 'kill'
- 'restart'
- 'stop'
+ aliases:
+ - health_on_failure
hooks_dir:
description:
- Each .json file in the path configures a hook for Podman containers.
@@ -493,6 +563,11 @@ options:
- Container host name. Sets the container host name that is available
inside the container.
type: str
+ hostuser:
+ description:
+ - Add a user account to /etc/passwd from the host to the container.
+ The Username or UID must exist on the host system.
+ type: str
http_proxy:
description:
- By default proxy environment variables are passed into the container if
@@ -522,6 +597,14 @@ options:
- Run an init inside the container that forwards signals and reaps
processes. The default is false.
type: bool
+ init_ctr:
+ description:
+ - (Pods only). When using pods, create an init style container,
+ which is run after the infra container is started but before regular pod containers are started.
+ type: str
+ choices:
+ - 'once'
+ - 'always'
init_path:
description:
- Path to the container-init binary.
@@ -542,6 +625,10 @@ options:
The address must be within the default CNI network's pool
(default 10.88.0.0/16).
type: str
+ ip6:
+ description:
+ - Specify a static IPv6 address for the container
+ type: str
ipc:
description:
- Default is to create a private IPC namespace (POSIX SysV IPC) for the
@@ -671,6 +758,12 @@ options:
This is a limitation that will be removed in a later release.
type: list
elements: str
+ aliases:
+ - network_alias
+ no_healthcheck:
+ description:
+ - Disable any defined healthchecks for container.
+ type: bool
no_hosts:
description:
- Do not create /etc/hosts for the container
@@ -685,23 +778,64 @@ options:
description:
- Tune the host's OOM preferences for containers (accepts -1000 to 1000)
type: int
+ os:
+ description:
+ - Override the OS, defaults to hosts, of the image to be pulled. For example, windows.
+ type: str
+ passwd:
+ description:
+ - Allow Podman to add entries to /etc/passwd and /etc/group when used in conjunction with the --user option.
+ This is used to override the Podman provided user setup in favor of entrypoint configurations
+ such as libnss-extrausers.
+ type: bool
+ passwd_entry:
+ description:
+ - Customize the entry that is written to the /etc/passwd file within the container when --passwd is used.
+ type: str
+ personality:
+ description:
+ - Personality sets the execution domain via Linux personality(2).
+ type: str
pid:
description:
- Set the PID mode for the container
type: str
aliases:
- pid_mode
+ pid_file:
+ description:
+ - When the pidfile location is specified, the container process' PID is written to the pidfile.
+ type: path
pids_limit:
description:
- Tune the container's PIDs limit. Set -1 to have unlimited PIDs for the
container.
type: str
+ platform:
+ description:
+ - Specify the platform for selecting the image.
+ type: str
pod:
description:
- Run container in an existing pod.
If you want podman to make the pod for you, prefix the pod name
with "new:"
type: str
+ pod_id_file:
+ description:
+ - Run container in an existing pod and read the pod's ID from the specified file.
+ When a container is run within a pod which has an infra-container,
+ the infra-container starts first.
+ type: path
+ preserve_fd:
+ description:
+ - Pass down to the process the additional file descriptors specified in the comma separated list.
+ type: list
+ elements: str
+ preserve_fds:
+ description:
+ - Pass down to the process N additional file descriptors (in addition to 0, 1, 2). The total FDs are 3\+N.
+ type: str
privileged:
description:
- Give extended privileges to this container. The default is false.
@@ -724,6 +858,15 @@ options:
- Publish all exposed ports to random ports on the host interfaces. The
default is false.
type: bool
+ pull:
+ description:
+ - Pull image policy. The default is 'missing'.
+ type: str
+ choices:
+ - 'missing'
+ - 'always'
+ - 'never'
+ - 'newer'
quadlet_dir:
description:
- Path to the directory to write quadlet file in.
@@ -740,6 +883,10 @@ options:
options as a list of lines to add.
type: list
elements: str
+ rdt_class:
+ description:
+ - Rdt-class sets the class of service (CLOS or COS) for the container to run in. Requires root.
+ type: str
read_only:
description:
- Mount the container's root filesystem as read only. Default is false
@@ -779,6 +926,15 @@ options:
- Seconds to wait before forcibly stopping the container when restarting. Use -1 for infinite wait.
Applies to "restarted" status.
type: str
+ retry:
+ description:
+ - Number of times to retry pulling or pushing images between the registry and local storage in case of failure.
+ Default is 3.
+ type: int
+ retry_delay:
+ description:
+ - Duration of delay between retry attempts when pulling or pushing images between the registry and local storage in case of failure.
+ type: str
rm:
description:
- Automatically remove the container when it exits. The default is false.
@@ -786,6 +942,11 @@ options:
aliases:
- remove
- auto_remove
+ rmi:
+ description:
+ - After exit of the container, remove the image unless another container is using it.
+ Implies --rm on the new container. The default is false.
+ type: bool
rootfs:
description:
- If true, the first argument refers to an exploded container on the file
@@ -803,6 +964,10 @@ options:
L(documentation,https://docs.podman.io/en/latest/markdown/podman-run.1.html#secret-secret-opt-opt) for more details.
type: list
elements: str
+ seccomp_policy:
+ description:
+ - Specify the policy to select the seccomp profile.
+ type: str
security_opt:
description:
- Security Options. For example security_opt "seccomp=unconfined"
@@ -817,6 +982,10 @@ options:
If you omit the unit, the system uses bytes. If you omit the size
entirely, the system uses 64m
type: str
+ shm_size_systemd:
+ description:
+ - Size of systemd-specific tmpfs mounts such as /run, /run/lock, /var/log/journal and /tmp.
+ type: str
sig_proxy:
description:
- Proxy signals sent to the podman run command to the container process.
@@ -853,6 +1022,11 @@ options:
description:
- Run container in systemd mode. The default is true.
type: str
+ timeout:
+ description:
+ - Maximum time (in seconds) a container is allowed to run before conmon sends it the kill signal.
+ By default containers run until they exit or are stopped by "podman stop".
+ type: int
timezone:
description:
- Set timezone in container. This flag takes area-based timezones,
@@ -861,6 +1035,10 @@ options:
See /usr/share/zoneinfo/ for valid timezones.
Remote connections use local containers.conf for defaults.
type: str
+ tls_verify:
+ description:
+ - Require HTTPS and verify certificates when pulling images.
+ type: bool
tmpfs:
description:
- Create a tmpfs mount. For example tmpfs
@@ -882,6 +1060,20 @@ options:
elements: str
aliases:
- ulimits
+ umask:
+ description:
+ - Set the umask inside the container. Defaults to 0022.
+ Remote connections use local containers.conf for defaults.
+ type: str
+ unsetenv:
+ description:
+ - Unset default environment variables for the container.
+ type: list
+ elements: str
+ unsetenv_all:
+ description:
+ - Unset all default environment variables for the container.
+ type: bool
user:
description:
- Sets the username or UID used and optionally the groupname or GID for
@@ -899,6 +1091,10 @@ options:
description:
- Set the UTS mode for the container
type: str
+ variant:
+ description:
+ - Use VARIANT instead of the default architecture variant of the container image.
+ type: str
volume:
description:
- Create a bind mount. If you specify, volume /HOST-DIR:/CONTAINER-DIR,
diff --git a/ansible_collections/containers/podman/plugins/modules/podman_image.py b/ansible_collections/containers/podman/plugins/modules/podman_image.py
index 7fcb0041a..a46a6c3c5 100644
--- a/ansible_collections/containers/podman/plugins/modules/podman_image.py
+++ b/ansible_collections/containers/podman/plugins/modules/podman_image.py
@@ -42,6 +42,10 @@ DOCUMENTATION = r'''
description: Whether or not to pull the image.
default: True
type: bool
+ pull_extra_args:
+ description:
+ - Extra arguments to pass to the pull command.
+ type: str
push:
description: Whether or not to push an image.
default: False
@@ -67,7 +71,8 @@ DOCUMENTATION = r'''
- quadlet
validate_certs:
description:
- - Require HTTPS and validate certificates when pulling or pushing. Also used during build if a pull or push is necessary.
+ - Require HTTPS and validate certificates when pulling or pushing.
+ Also used during build if a pull or push is necessary.
type: bool
aliases:
- tlsverify
@@ -94,9 +99,15 @@ DOCUMENTATION = r'''
- build_args
- buildargs
suboptions:
+ container_file:
+ description:
+ - Content of the Containerfile to use for building the image.
+ Mutually exclusive with the C(file) option which is path to the existing Containerfile.
+ type: str
file:
description:
- Path to the Containerfile if it is not in the build context directory.
+ Mutually exclusive with the C(container_file) option.
type: path
volume:
description:
@@ -105,7 +116,8 @@ DOCUMENTATION = r'''
elements: str
annotation:
description:
- - Dictionary of key=value pairs to add to the image. Only works with OCI images. Ignored for Docker containers.
+ - Dictionary of key=value pairs to add to the image. Only works with OCI images.
+ Ignored for Docker containers.
type: dict
force_rm:
description:
@@ -148,7 +160,7 @@ DOCUMENTATION = r'''
type: bool
format:
description:
- - Manifest type to use when pushing an image using the 'dir' transport (default is manifest type of source).
+ - Manifest type to use when pushing an image using the 'dir' transport (default is manifest type of source)
type: str
choices:
- oci
@@ -168,14 +180,19 @@ DOCUMENTATION = r'''
- destination
transport:
description:
- - Transport to use when pushing in image. If no transport is set, will attempt to push to a remote registry.
+ - 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
- docker-archive
- docker-daemon
- oci-archive
- ostree
+ extra_args:
+ description:
+ - Extra args to pass to push, if executed. Does not idempotently check for new push args.
+ type: str
quadlet_dir:
description:
- Path to the directory to write quadlet file in.
@@ -300,6 +317,15 @@ EXAMPLES = r"""
name: nginx
arch: amd64
+- name: Build a container from file inline
+ containers.podman.podman_image:
+ name: mycustom_image
+ state: build
+ build:
+ container_file: |-
+ FROM alpine:latest
+ CMD echo "Hello, World!"
+
- name: Create a quadlet file for an image
containers.podman.podman_image:
name: docker.io/library/alpine:latest
@@ -333,7 +359,7 @@ RETURN = r"""
"/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",
+ "PATH=/opt/bitnami/java/bin:/opt/bitnami/wildfly/bin:/opt/bitnami/nami/bin:...",
"IMAGE_OS=debian-9",
"NAMI_VERSION=1.0.0-1",
"GPG_KEY_SERVERS_LIST=ha.pool.sks-keyservers.net",
@@ -373,10 +399,10 @@ RETURN = r"""
"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"
+ "LowerDir": "/var/lib/containers/storage/overlay/142c1beadf1bb09fbd929465e..../diff:/var/lib/containers/s",
+ "MergedDir": "/var/lib/containers/storage/overlay/9aa10191f5bddb59e28508e721fdeb43505e5b395845fa99/merged",
+ "UpperDir": "/var/lib/containers/storage/overlay/9aa10191f5bddb59e28508e721fdeb43505e5b395845fa99/diff",
+ "WorkDir": "/var/lib/containers/storage/overlay/9aa10191f5bddb59e28508e721fdeb43505e5b395845fa99/work"
},
"Name": "overlay"
},
@@ -434,9 +460,12 @@ RETURN = r"""
]
"""
-import json
-import re
-import shlex
+import json # noqa: E402
+import os # noqa: E402
+import re # noqa: E402
+import shlex # noqa: E402
+import tempfile # noqa: E402
+import time # noqa: E402
from ansible.module_utils._text import to_native
from ansible.module_utils.basic import AnsibleModule
@@ -456,6 +485,7 @@ class PodmanImageManager(object):
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.pull_extra_args = self.module.params.get('pull_extra_args')
self.push = self.module.params.get('push')
self.path = self.module.params.get('path')
self.force = self.module.params.get('force')
@@ -509,7 +539,7 @@ class PodmanImageManager(object):
if not layer_ids:
layer_ids = lines.splitlines()
- return (layer_ids[-1])
+ return layer_ids[-1]
def present(self):
image = self.find_image()
@@ -520,9 +550,18 @@ class PodmanImageManager(object):
digest_before = None
if not image or self.force:
- if self.path:
+ if self.state == 'build' or self.path:
# Build the image
- self.results['actions'].append('Built image {image_name} from {path}'.format(image_name=self.image_name, path=self.path))
+ build_file = self.build.get('file') if self.build else None
+ container_file_txt = self.build.get('container_file') if self.build else None
+ if build_file and container_file_txt:
+ self.module.fail_json(msg='Cannot specify both build file and container file content!')
+ if not self.path and build_file:
+ self.path = os.path.dirname(build_file)
+ elif not self.path and not build_file and not container_file_txt:
+ self.module.fail_json(msg='Path to build context or file is required when building an image')
+ self.results['actions'].append('Built image {image_name} from {path}'.format(
+ image_name=self.image_name, path=self.path or 'default context'))
if not self.module.check_mode:
self.results['image'], self.results['stdout'] = self.build_image()
image = self.results['image']
@@ -541,16 +580,8 @@ class PodmanImageManager(object):
self.results['changed'] = True
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'], output = self.push_image()
- self.results['stdout'] += "\n" + output
+ self.results['image'], output = self.push_image()
+ self.results['stdout'] += "\n" + output
if image and not self.results.get('image'):
self.results['image'] = image
@@ -654,13 +685,18 @@ class PodmanImageManager(object):
if self.ca_cert_dir:
args.extend(['--cert-dir', self.ca_cert_dir])
+ if self.pull_extra_args:
+ args.extend(shlex.split(self.pull_extra_args))
+
rc, out, err = self._run(args, ignore_errors=True)
if rc != 0:
if not self.pull:
- self.module.fail_json(msg='Failed to find image {image_name} locally, image pull set to {pull_bool}'.format(
- pull_bool=self.pull, image_name=image_name))
+ self.module.fail_json(
+ msg='Failed to find image {image_name} locally, image pull set to {pull_bool}'.format(
+ pull_bool=self.pull, image_name=image_name))
else:
- self.module.fail_json(msg='Failed to pull image {image_name}'.format(image_name=image_name))
+ 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):
@@ -697,6 +733,17 @@ class PodmanImageManager(object):
containerfile = self.build.get('file')
if containerfile:
args.extend(['--file', containerfile])
+ container_file_txt = self.build.get('container_file')
+ if container_file_txt:
+ # create a temporarly file with the content of the Containerfile
+ if self.path:
+ container_file_path = os.path.join(self.path, 'Containerfile.generated_by_ansible_%s' % time.time())
+ else:
+ container_file_path = os.path.join(
+ tempfile.gettempdir(), 'Containerfile.generated_by_ansible_%s' % time.time())
+ with open(container_file_path, 'w') as f:
+ f.write(container_file_txt)
+ args.extend(['--file', container_file_path])
volume = self.build.get('volume')
if volume:
@@ -717,13 +764,16 @@ class PodmanImageManager(object):
target = self.build.get('target')
if target:
args.extend(['--target', target])
-
- args.append(self.path)
+ if self.path:
+ 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))
-
+ self.module.fail_json(msg="Failed to build image {image}: {out} {err}".format(
+ image=self.image_name, out=out, err=err))
+ # remove the temporary file if it was created
+ if container_file_txt:
+ os.remove(container_file_path)
last_id = self._get_id_from_output(out, startswith='-->')
return self.inspect_image(last_id), out + err
@@ -760,49 +810,55 @@ class PodmanImageManager(object):
if sign_by_key:
args.extend(['--sign-by', sign_by_key])
+ push_extra_args = self.push_args.get('extra_args')
+ if push_extra_args:
+ args.extend(shlex.split(push_extra_args))
+
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))
+ transport = self.push_args.get('transport')
- if dest and dest.endswith('/'):
- dest = dest[:-1]
+ if dest is None:
+ dest = self.image_name
- 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), out + err
+ if transport == 'docker-daemon' and ":" not in dest:
+ dest_format_string = '{transport}:{dest}:latest'
+ dest_string = dest_format_string.format(transport=transport, name=self.name, dest=dest)
+ else:
+ dest_string = dest
+ # In case of dest as a repository with org name only, append image name to it
+ if ":" not in dest and "@" not in dest and len(dest.rstrip("/").split("/")) == 2:
+ dest_string = dest.rstrip("/") + "/" + self.image_name
+
+ if "/" not in dest_string and "@" not in dest_string and "docker-daemon" not in dest_string:
+ self.module.fail_json(msg="Destination must be a full URL or path to a directory.")
+
+ args.append(dest_string)
+ self.module.log("PODMAN-IMAGE-DEBUG: Pushing image {image_name} to {dest_string}".format(
+ image_name=self.image_name, dest_string=dest_string))
+ self.results['actions'].append(" ".join(args))
+ self.results['podman_actions'].append(" ".join([self.executable] + args))
+ self.results['changed'] = True
+ out, err = '', ''
+ if not self.module.check_mode:
+ rc, out, err = self._run(args, ignore_errors=True)
+ if rc != 0:
+ self.module.fail_json(msg="Failed to push image {image_name}".format(
+ image_name=self.image_name),
+ stdout=out, stderr=err,
+ actions=self.results['actions'],
+ podman_actions=self.results['podman_actions'])
+
+ return self.inspect_image(self.image_name), out + err
def remove_image(self, image_name=None):
if image_name is None:
@@ -813,7 +869,8 @@ class PodmanImageManager(object):
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))
+ self.module.fail_json(msg='Failed to remove image {image_name}. {err}'.format(
+ image_name=image_name, err=err))
return out
def remove_image_id(self, image_id=None):
@@ -847,6 +904,7 @@ def main():
arch=dict(type='str'),
tag=dict(type='str', default='latest'),
pull=dict(type='bool', default=True),
+ pull_extra_args=dict(type='str'),
push=dict(type='bool', default=False),
path=dict(type='str'),
force=dict(type='bool', default=False),
@@ -868,6 +926,7 @@ def main():
annotation=dict(type='dict'),
force_rm=dict(type='bool', default=False),
file=dict(type='path'),
+ container_file=dict(type='str'),
format=dict(
type='str',
choices=['oci', 'docker'],
@@ -889,6 +948,7 @@ def main():
remove_signatures=dict(type='bool'),
sign_by=dict(type='str'),
dest=dict(type='str', aliases=['destination'],),
+ extra_args=dict(type='str'),
transport=dict(
type='str',
choices=[
@@ -897,6 +957,7 @@ def main():
'docker-daemon',
'oci-archive',
'ostree',
+ 'docker'
]
),
),
diff --git a/ansible_collections/containers/podman/plugins/modules/podman_network.py b/ansible_collections/containers/podman/plugins/modules/podman_network.py
index 37bfefede..7623fffc1 100644
--- a/ansible_collections/containers/podman/plugins/modules/podman_network.py
+++ b/ansible_collections/containers/podman/plugins/modules/podman_network.py
@@ -33,6 +33,12 @@ options:
description:
- disable dns plugin (default "false")
type: bool
+ dns:
+ description:
+ - Set network-scoped DNS resolver/nameserver for containers in this network.
+ If not set, the host servers from /etc/resolv.conf is used.
+ type: list
+ elements: str
driver:
description:
- Driver to manage the network (default "bridge")
@@ -61,11 +67,27 @@ options:
description:
- Allocate container IP from range
type: str
+ ipam_driver:
+ description:
+ - Set the ipam driver (IP Address Management Driver) for the network.
+ When unset podman chooses an ipam driver automatically based on the network driver
+ type: str
+ choices:
+ - host-local
+ - dhcp
+ - none
ipv6:
description:
- Enable IPv6 (Dual Stack) networking. You must pass a IPv6 subnet.
The subnet option must be used with the ipv6 option.
+ Idempotency is not supported because it generates subnets randomly.
type: bool
+ route:
+ description:
+ - A static route in the format <destination in CIDR notation>,<gateway>,<route metric (optional)>.
+ This route will be added to every container in this network.
+ type: list
+ elements: str
subnet:
description:
- Subnet in CIDR format
@@ -74,6 +96,29 @@ options:
description:
- Create a Macvlan connection based on this device
type: str
+ net_config:
+ description:
+ - List of dictionaries with network configuration.
+ Each dictionary should contain 'subnet' and 'gateway' keys.
+ 'ip_range' is optional.
+ type: list
+ elements: dict
+ suboptions:
+ subnet:
+ description:
+ - Subnet in CIDR format
+ type: str
+ required: true
+ gateway:
+ description:
+ - Gateway for the subnet
+ type: str
+ required: true
+ ip_range:
+ description:
+ - Allocate container IP from range
+ type: str
+ required: false
opt:
description:
- Add network options. Currently 'vlan' and 'mtu' are supported.
@@ -297,6 +342,11 @@ class PodmanNetworkModuleParams:
def addparam_gateway(self, c):
return c + ['--gateway', self.params['gateway']]
+ def addparam_dns(self, c):
+ for dns in self.params['dns']:
+ c += ['--dns', dns]
+ return c
+
def addparam_driver(self, c):
return c + ['--driver', self.params['driver']]
@@ -312,6 +362,13 @@ class PodmanNetworkModuleParams:
def addparam_macvlan(self, c):
return c + ['--macvlan', self.params['macvlan']]
+ def addparam_net_config(self, c):
+ for net in self.params['net_config']:
+ for kw in ('subnet', 'gateway', 'ip_range'):
+ if kw in net and net[kw]:
+ c += ['--%s=%s' % (kw.replace('_', '-'), net[kw])]
+ return c
+
def addparam_interface_name(self, c):
return c + ['--interface-name', self.params['interface_name']]
@@ -326,6 +383,14 @@ class PodmanNetworkModuleParams:
for k in opt])]
return c
+ def addparam_route(self, c):
+ for route in self.params['route']:
+ c += ['--route', route]
+ return c
+
+ def addparam_ipam_driver(self, c):
+ return c + ['--ipam-driver=%s' % self.params['ipam_driver']]
+
def addparam_disable_dns(self, c):
return c + ['--disable-dns=%s' % self.params['disable_dns']]
@@ -337,7 +402,6 @@ class PodmanNetworkDefaults:
self.defaults = {
'driver': 'bridge',
'internal': False,
- 'ipv6': False
}
def default_dict(self):
@@ -385,32 +449,45 @@ class PodmanNetworkDiff:
before = after = self.params['disable_dns']
return self._diff_update_and_compare('disable_dns', before, after)
+ def diffparam_dns(self):
+ before = self.info.get('network_dns_servers', [])
+ after = self.params['dns'] or []
+ return self._diff_update_and_compare('dns', sorted(before), sorted(after))
+
def diffparam_driver(self):
# Currently only bridge is supported
before = after = 'bridge'
return self._diff_update_and_compare('driver', before, after)
def diffparam_ipv6(self):
- if LooseVersion(self.version) >= LooseVersion('4.0.0'):
- before = self.info.get('ipv6_enabled', False)
- after = self.params['ipv6']
- return self._diff_update_and_compare('ipv6', before, after)
- before = after = ''
- return self._diff_update_and_compare('ipv6', before, after)
+ # We don't support dual stack because it generates subnets randomly
+ return self._diff_update_and_compare('ipv6', '', '')
def diffparam_gateway(self):
# Disable idempotency of subnet for v4, subnets are added automatically
# TODO(sshnaidm): check if it's still the issue in v5
- if LooseVersion(self.version) >= LooseVersion('4.0.0'):
- return self._diff_update_and_compare('gateway', '', '')
- try:
- before = self.info['plugins'][0]['ipam']['ranges'][0][0]['gateway']
- except (IndexError, KeyError):
- before = ''
- after = before
- if self.params['gateway'] is not None:
+ if LooseVersion(self.version) < LooseVersion('4.0.0'):
+ 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)
+ else:
+ before_subs = self.info.get('subnets')
after = self.params['gateway']
- return self._diff_update_and_compare('gateway', before, after)
+ if not before_subs:
+ before = None
+ if before_subs:
+ if len(before_subs) > 1 and after:
+ return self._diff_update_and_compare(
+ 'gateway', ",".join([i['gateway'] for i in before_subs]), after)
+ before = [i.get('gateway') for i in before_subs][0]
+ if not after:
+ after = before
+ return self._diff_update_and_compare('gateway', before, after)
def diffparam_internal(self):
if LooseVersion(self.version) >= LooseVersion('4.0.0'):
@@ -429,21 +506,62 @@ class PodmanNetworkDiff:
before = after = ''
return self._diff_update_and_compare('ip_range', before, after)
- def diffparam_subnet(self):
- # Disable idempotency of subnet for v4, subnets are added automatically
- # TODO(sshnaidm): check if it's still the issue in v5
- if LooseVersion(self.version) >= LooseVersion('4.0.0'):
- return self._diff_update_and_compare('subnet', '', '')
- try:
- before = self.info['plugins'][0]['ipam']['ranges'][0][0]['subnet']
- except (IndexError, KeyError):
+ def diffparam_ipam_driver(self):
+ before = self.info.get("ipam_options", {}).get("driver", "")
+ after = self.params['ipam_driver']
+ if not after:
+ after = before
+ return self._diff_update_and_compare('ipam_driver', before, after)
+
+ def diffparam_net_config(self):
+ after = self.params['net_config']
+ if not after:
+ return self._diff_update_and_compare('net_config', '', '')
+ before_subs = self.info.get('subnets', [])
+ if before_subs:
+ before = ":".join(sorted([",".join([i['subnet'], i['gateway']]).rstrip(",") for i in before_subs]))
+ else:
before = ''
- after = before
- if self.params['subnet'] is not None:
+ after = ":".join(sorted([",".join([i['subnet'], i['gateway']]).rstrip(",") for i in after]))
+ return self._diff_update_and_compare('net_config', before, after)
+
+ def diffparam_route(self):
+ routes = self.info.get('routes', [])
+ if routes:
+ before = [",".join([
+ r['destination'], r['gateway'], str(r.get('metric', ''))]).rstrip(",") for r in routes]
+ else:
+ before = []
+ after = self.params['route'] or []
+ return self._diff_update_and_compare('route', sorted(before), sorted(after))
+
+ def diffparam_subnet(self):
+ # Disable idempotency of subnet for v3 and below
+ if LooseVersion(self.version) < LooseVersion('4.0.0'):
+ 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']
+ if HAS_IP_ADDRESS_MODULE:
+ after = ipaddress.ip_network(after).compressed
+ return self._diff_update_and_compare('subnet', before, after)
+ else:
+ if self.params['ipv6'] is not None:
+ # We can't support dual stack, it generates subnets randomly
+ return self._diff_update_and_compare('subnet', '', '')
after = self.params['subnet']
- if HAS_IP_ADDRESS_MODULE:
- after = ipaddress.ip_network(after).compressed
- return self._diff_update_and_compare('subnet', before, after)
+ if after is None:
+ # We can't guess what subnet was used before by default
+ return self._diff_update_and_compare('subnet', '', '')
+ before = self.info.get('subnets')
+ if before:
+ if len(before) > 1 and after:
+ return self._diff_update_and_compare('subnet', ",".join([i['subnet'] for i in before]), after)
+ before = [i['subnet'] for i in before][0]
+ return self._diff_update_and_compare('subnet', before, after)
def diffparam_macvlan(self):
before = after = ''
@@ -694,12 +812,15 @@ def main():
choices=['present', 'absent', 'quadlet']),
name=dict(type='str', required=True),
disable_dns=dict(type='bool', required=False),
+ dns=dict(type='list', elements='str', required=False),
driver=dict(type='str', required=False),
force=dict(type='bool', default=False),
gateway=dict(type='str', required=False),
interface_name=dict(type='str', required=False),
internal=dict(type='bool', required=False),
ip_range=dict(type='str', required=False),
+ ipam_driver=dict(type='str', required=False,
+ choices=['host-local', 'dhcp', 'none']),
ipv6=dict(type='bool', required=False),
subnet=dict(type='str', required=False),
macvlan=dict(type='str', required=False),
@@ -715,14 +836,23 @@ def main():
executable=dict(type='str', required=False, default='podman'),
debug=dict(type='bool', default=False),
recreate=dict(type='bool', default=False),
+ route=dict(type='list', elements='str', required=False),
quadlet_dir=dict(type='path', required=False),
quadlet_filename=dict(type='str', required=False),
quadlet_options=dict(type='list', elements='str', required=False),
+ net_config=dict(type='list', required=False, elements='dict',
+ options=dict(
+ subnet=dict(type='str', required=True),
+ gateway=dict(type='str', required=True),
+ ip_range=dict(type='str', required=False),
+ )),
),
required_by=dict( # for IP range and GW to set 'subnet' is required
ip_range=('subnet'),
gateway=('subnet'),
- ))
+ ),
+ # define or subnet or net config
+ mutually_exclusive=[['subnet', 'net_config']])
PodmanNetworkManager(module).execute()
diff --git a/ansible_collections/containers/podman/plugins/modules/podman_pod.py b/ansible_collections/containers/podman/plugins/modules/podman_pod.py
index a975921ea..cdf728243 100644
--- a/ansible_collections/containers/podman/plugins/modules/podman_pod.py
+++ b/ansible_collections/containers/podman/plugins/modules/podman_pod.py
@@ -117,6 +117,8 @@ options:
all containers in the pod.
type: list
elements: str
+ aliases:
+ - dns_option
required: false
dns_search:
description:
@@ -125,6 +127,14 @@ options:
type: list
elements: str
required: false
+ exit_policy:
+ description:
+ - Set the exit policy of the pod when the last container exits. Supported policies are stop and continue
+ choices:
+ - stop
+ - continue
+ type: str
+ required: false
generate_systemd:
description:
- Generate systemd unit file for container.
@@ -227,6 +237,11 @@ options:
elements: str
required: false
type: list
+ gpus:
+ description:
+ - GPU devices to add to the container ('all' to pass all GPUs).
+ type: str
+ required: false
hostname:
description:
- Set a hostname to the pod
@@ -266,6 +281,11 @@ options:
- Set a static IP for the pod's shared network.
type: str
required: false
+ ip6:
+ description:
+ - Set a static IPv6 for the pod's shared network.
+ type: str
+ required: false
label:
description:
- Add metadata to a pod, pass dictionary of label keys and values.
@@ -357,6 +377,16 @@ options:
options as a list of lines to add.
type: list
elements: str
+ restart_policy:
+ description:
+ - Restart policy to follow when containers exit.
+ type: str
+ security_opt:
+ description:
+ - Security options for the pod.
+ type: list
+ elements: str
+ required: false
share:
description:
- A comma delimited list of kernel namespaces to share. If none or "" is specified,
@@ -364,6 +394,30 @@ options:
user, uts.
type: str
required: false
+ share_parent:
+ description:
+ - This boolean determines whether or not all containers entering the pod use the pod as their cgroup parent.
+ The default value of this option in Podman is true.
+ type: bool
+ required: false
+ shm_size:
+ description:
+ - Set the size of the /dev/shm shared memory space.
+ A unit can be b (bytes), k (kibibytes), m (mebibytes), or g (gibibytes).
+ If the unit is omitted, the system uses bytes.
+ If the size is omitted, the default is 64m.
+ When size is 0, there is no limit on the amount of memory used for IPC by the pod.
+ type: str
+ required: false
+ shm_size_systemd:
+ description:
+ - Size of systemd-specific tmpfs mounts such as /run, /run/lock, /var/log/journal and /tmp.
+ A unit can be b (bytes), k (kibibytes), m (mebibytes), or g (gibibytes).
+ If the unit is omitted, the system uses bytes.
+ If the size is omitted, the default is 64m.
+ When size is 0, the usage is limited to 50 percents of the host's available memory.
+ type: str
+ required: false
subgidname:
description:
- Name for GID map from the /etc/subgid file. Using this flag will run the container
@@ -377,6 +431,11 @@ options:
This flag conflicts with `userns` and `uidmap`.
required: false
type: str
+ sysctl:
+ description:
+ - Set kernel parameters for the pod.
+ type: dict
+ required: false
uidmap:
description:
- Run the container in a new user namespace using the supplied mapping.
@@ -393,6 +452,11 @@ options:
An empty value ("") means user namespaces are disabled.
required: false
type: str
+ uts:
+ description:
+ - Set the UTS namespace mode for the pod.
+ required: false
+ type: str
volume:
description:
- Create a bind mount.
@@ -401,6 +465,12 @@ options:
elements: str
required: false
type: list
+ volumes_from:
+ description:
+ - Mount volumes from the specified container.
+ elements: str
+ required: false
+ type: list
executable:
description:
- Path to C(podman) executable if it is not in the C($PATH) on the
@@ -450,7 +520,7 @@ pod:
'''
-EXAMPLES = '''
+EXAMPLES = r'''
# What modules does for example
- containers.podman.podman_pod:
name: pod1
@@ -465,6 +535,62 @@ EXAMPLES = '''
state: started
publish: "127.0.0.1::80"
+# Full workflow example with pod and containers
+- name: Create a pod with parameters
+ containers.podman.podman_pod:
+ name: mypod
+ state: created
+ network: host
+ share: net
+ userns: auto
+ security_opt:
+ - seccomp=unconfined
+ - apparmor=unconfined
+ hostname: mypod
+ dns:
+ - 1.1.1.1
+ volumes:
+ - /tmp:/tmp/:ro
+ label:
+ key: cval
+ otherkey: kddkdk
+ somekey: someval
+ add_host:
+ - "google:5.5.5.5"
+
+- name: Create containers attached to the pod
+ containers.podman.podman_container:
+ name: "{{ item }}"
+ state: created
+ pod: mypod
+ image: alpine
+ command: sleep 1h
+ loop:
+ - "container1"
+ - "container2"
+
+- name: Start pod
+ containers.podman.podman_pod:
+ name: mypod
+ state: started
+ network: host
+ share: net
+ userns: auto
+ security_opt:
+ - seccomp=unconfined
+ - apparmor=unconfined
+ hostname: mypod
+ dns:
+ - 1.1.1.1
+ volumes:
+ - /tmp:/tmp/:ro
+ label:
+ key: cval
+ otherkey: kddkdk
+ somekey: someval
+ add_host:
+ - "google:5.5.5.5"
+
# Create a Quadlet file for a pod
- containers.podman.podman_pod:
name: qpod
diff --git a/ansible_collections/containers/podman/plugins/modules/podman_search.py b/ansible_collections/containers/podman/plugins/modules/podman_search.py
new file mode 100644
index 000000000..128e3ce03
--- /dev/null
+++ b/ansible_collections/containers/podman/plugins/modules/podman_search.py
@@ -0,0 +1,131 @@
+#!/usr/bin/python
+# Copyright (c) 2024 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_search
+author:
+ - Derek Waters (@derekwaters)
+short_description: Search for remote images using podman
+notes:
+ - Podman may required elevated privileges in order to run properly.
+description:
+ - Search for remote 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
+ term:
+ description:
+ - The search term to look for. Will search all default registries unless a registry is defined in the search term.
+ type: str
+ required: True
+ limit:
+ description:
+ - Limit the number of image results returned from the search (per image registry)
+ required: False
+ default: 25
+ type: int
+ list_tags:
+ description:
+ - Whether or not to return the list of tags associated with each image
+ required: False
+ default: False
+ type: bool
+
+'''
+
+EXAMPLES = r"""
+- name: Search for any rhel images
+ containers.podman.podman_search:
+ term: "rhel"
+ limit: 3
+
+- name: Gather info on a specific remote image
+ containers.podman.podman_search:
+ term: "myimageregistry.com/ansible-automation-platform/ee-minimal-rhel8"
+
+- name: Gather tag info on a known remote image
+ containers.podman.podman_search:
+ term: "myimageregistry.com/ansible-automation-platform/ee-minimal-rhel8"
+ list_tags: True
+"""
+
+RETURN = r"""
+images:
+ description: info from all or specified images
+ returned: always
+ type: list
+ sample: [
+ {
+ "Automated": "",
+ "Description": "Red Hat Enterprise Linux Atomic Image is a minimal, fully supported base image.",
+ "Index": "registry.access.redhat.com",
+ "Name": "registry.access.redhat.com/rhel7-atomic",
+ "Official": "",
+ "Stars": 0,
+ "Tags": ["1.0", "1.1", "1.1.1-devel"]
+ }
+ ]
+"""
+
+import json
+
+from ansible.module_utils.basic import AnsibleModule
+
+
+def search_images(module, executable, term, limit, list_tags):
+ command = [executable, 'search', term, '--format', 'json']
+ command.extend(['--limit', "{0}".format(limit)])
+ if list_tags:
+ command.extend(['--list-tags'])
+
+ rc, out, err = module.run_command(command)
+
+ if rc != 0:
+ module.fail_json(msg="Unable to gather info for '{0}': {1}".format(term, err))
+ return out
+
+
+def main():
+ module = AnsibleModule(
+ argument_spec=dict(
+ executable=dict(type='str', default='podman'),
+ term=dict(type='str', required=True),
+ limit=dict(type='int', required=False, default=25),
+ list_tags=dict(type='bool', required=False, default=False)
+ ),
+ supports_check_mode=True,
+ )
+
+ executable = module.params['executable']
+ term = module.params.get('term')
+ limit = module.params.get('limit')
+ list_tags = module.params.get('list_tags')
+ executable = module.get_bin_path(executable, required=True)
+
+ result_str = search_images(module, executable, term, limit, list_tags)
+ if result_str == "":
+ results = []
+ else:
+ try:
+ results = json.loads(result_str)
+ except json.decoder.JSONDecodeError:
+ module.fail_json(msg='Failed to parse JSON output from podman search: {out}'.format(out=result_str))
+
+ results = dict(
+ changed=False,
+ images=results
+ )
+
+ module.exit_json(**results)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/containers/podman/plugins/modules/podman_secret.py b/ansible_collections/containers/podman/plugins/modules/podman_secret.py
index a31aae9dc..76b10ad39 100644
--- a/ansible_collections/containers/podman/plugins/modules/podman_secret.py
+++ b/ansible_collections/containers/podman/plugins/modules/podman_secret.py
@@ -21,6 +21,7 @@ options:
data:
description:
- The value of the secret. Required when C(state) is C(present).
+ Mutually exclusive with C(env) and C(path).
type: str
driver:
description:
@@ -31,6 +32,11 @@ options:
description:
- Driver-specific key-value options.
type: dict
+ env:
+ description:
+ - The name of the environment variable that contains the secret.
+ Mutually exclusive with C(data) and C(path).
+ type: str
executable:
description:
- Path to C(podman) executable if it is not in the C($PATH) on the
@@ -53,6 +59,11 @@ options:
- The name of the secret.
required: True
type: str
+ path:
+ description:
+ - Path to the file that contains the secret.
+ Mutually exclusive with C(data) and C(env).
+ type: path
state:
description:
- Whether to create or remove the named secret.
@@ -67,7 +78,7 @@ options:
type: dict
debug:
description:
- - Enable debug mode for module.
+ - Enable debug mode for module. It prints secrets diff.
type: bool
default: False
'''
@@ -99,6 +110,8 @@ EXAMPLES = r"""
name: mysecret
"""
+import os
+
from ansible.module_utils.basic import AnsibleModule
from ansible_collections.containers.podman.plugins.module_utils.podman.common import LooseVersion
from ansible_collections.containers.podman.plugins.module_utils.podman.common import get_podman_version
@@ -116,14 +129,15 @@ def podman_secret_exists(module, executable, name, version):
return rc == 0
-def need_update(module, executable, name, data, driver, driver_opts, debug, labels):
-
+def need_update(module, executable, name, data, path, env, skip, driver, driver_opts, debug, labels):
cmd = [executable, 'secret', 'inspect', '--showsecret', name]
rc, out, err = module.run_command(cmd)
if rc != 0:
if debug:
module.log("PODMAN-SECRET-DEBUG: Unable to get secret info: %s" % err)
return True
+ if skip:
+ return False
try:
secret = module.from_json(out)[0]
# We support only file driver for now
@@ -131,10 +145,37 @@ def need_update(module, executable, name, data, driver, driver_opts, debug, labe
if debug:
module.log("PODMAN-SECRET-DEBUG: Idempotency of driver %s is not supported" % driver)
return True
- if secret['SecretData'] != data:
- diff['after'] = "<different-secret>"
- diff['before'] = "<secret>"
- return True
+ if data:
+ if secret['SecretData'] != data:
+ if debug:
+ diff['after'] = data
+ diff['before'] = secret['SecretData']
+ else:
+ diff['after'] = "<different-secret>"
+ diff['before'] = "<secret>"
+ return True
+ if path:
+ with open(path, 'rb') as f:
+ text = f.read().decode('utf-8')
+ if secret['SecretData'] != text:
+ if debug:
+ diff['after'] = text
+ diff['before'] = secret['SecretData']
+ else:
+ diff['after'] = "<different-secret>"
+ diff['before'] = "<secret>"
+ return True
+ if env:
+ env_data = os.environ.get(env)
+ if secret['SecretData'] != env_data:
+ if debug:
+ diff['after'] = env_data
+ diff['before'] = secret['SecretData']
+ else:
+ diff['after'] = "<different-secret>"
+ diff['before'] = "<secret>"
+ return True
+
if driver_opts:
for k, v in driver_opts.items():
if secret['Spec']['Driver']['Options'].get(k) != v:
@@ -154,13 +195,13 @@ def need_update(module, executable, name, data, driver, driver_opts, debug, labe
return False
-def podman_secret_create(module, executable, name, data, force, skip,
+def podman_secret_create(module, executable, name, data, path, env, force, skip,
driver, driver_opts, debug, labels):
podman_version = get_podman_version(module, fail=False)
if (podman_version is not None and
LooseVersion(podman_version) >= LooseVersion('4.7.0')
and (driver is None or driver == 'file')):
- if not skip and need_update(module, executable, name, data, driver, driver_opts, debug, labels):
+ if need_update(module, executable, name, data, path, env, skip, driver, driver_opts, debug, labels):
podman_secret_remove(module, executable, name)
else:
return {"changed": False}
@@ -182,9 +223,20 @@ def podman_secret_create(module, executable, name, data, force, skip,
cmd.append('--label')
cmd.append("=".join([k, v]))
cmd.append(name)
- cmd.append('-')
+ if data:
+ cmd.append('-')
+ elif path:
+ cmd.append(path)
+ elif env:
+ if os.environ.get(env) is None:
+ module.fail_json(msg="Environment variable %s is not set" % env)
+ cmd.append("--env")
+ cmd.append(env)
- rc, out, err = module.run_command(cmd, data=data, binary_data=True)
+ if data:
+ rc, out, err = module.run_command(cmd, data=data, binary_data=True)
+ else:
+ rc, out, err = module.run_command(cmd)
if rc != 0:
module.fail_json(msg="Unable to create secret: %s" % err)
@@ -219,6 +271,8 @@ def main():
state=dict(type='str', default='present', choices=['absent', 'present']),
name=dict(type='str', required=True),
data=dict(type='str', no_log=True),
+ env=dict(type='str'),
+ path=dict(type='path'),
force=dict(type='bool', default=False),
skip_existing=dict(type='bool', default=False),
driver=dict(type='str'),
@@ -226,6 +280,8 @@ def main():
labels=dict(type='dict'),
debug=dict(type='bool', default=False),
),
+ required_if=[('state', 'present', ['path', 'env', 'data'], True)],
+ mutually_exclusive=[['path', 'env', 'data']],
)
state = module.params['state']
@@ -234,16 +290,16 @@ def main():
if state == 'present':
data = module.params['data']
- if data is None:
- raise Exception("'data' is required when 'state' is 'present'")
force = module.params['force']
skip = module.params['skip_existing']
driver = module.params['driver']
driver_opts = module.params['driver_opts']
debug = module.params['debug']
labels = module.params['labels']
+ path = module.params['path']
+ env = module.params['env']
results = podman_secret_create(module, executable,
- name, data, force, skip,
+ name, data, path, env, force, skip,
driver, driver_opts, debug, labels)
else:
results = podman_secret_remove(module, executable, name)
diff --git a/ansible_collections/containers/podman/plugins/modules/podman_volume.py b/ansible_collections/containers/podman/plugins/modules/podman_volume.py
index 0b990354a..cb958cc50 100644
--- a/ansible_collections/containers/podman/plugins/modules/podman_volume.py
+++ b/ansible_collections/containers/podman/plugins/modules/podman_volume.py
@@ -24,6 +24,8 @@ options:
choices:
- present
- absent
+ - mounted
+ - unmounted
- quadlet
recreate:
description:
@@ -131,6 +133,7 @@ EXAMPLES = '''
'''
# noqa: F402
import json # 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
@@ -160,7 +163,7 @@ class PodmanVolumeModuleParams:
Returns:
list -- list of byte strings for Popen command
"""
- if self.action in ['delete']:
+ if self.action in ['delete', 'mount', 'unmount']:
return self._simple_action()
if self.action in ['create']:
return self._create_action()
@@ -169,6 +172,12 @@ class PodmanVolumeModuleParams:
if self.action == 'delete':
cmd = ['rm', '-f', self.params['name']]
return [to_bytes(i, errors='surrogate_or_strict') for i in cmd]
+ if self.action == 'mount':
+ cmd = ['mount', self.params['name']]
+ return [to_bytes(i, errors='surrogate_or_strict') for i in cmd]
+ if self.action == 'unmount':
+ cmd = ['unmount', 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']]
@@ -326,6 +335,7 @@ class PodmanVolume:
self.module = module
self.name = name
self.stdout, self.stderr = '', ''
+ self.mount_point = None
self.info = self.get_info()
self.version = self._get_podman_version()
self.diff = {}
@@ -380,7 +390,7 @@ class PodmanVolume:
"""Perform action with volume.
Arguments:
- action {str} -- action to perform - create, stop, delete
+ action {str} -- action to perform - create, delete, mount, unmout
"""
b_command = PodmanVolumeModuleParams(action,
self.module.params,
@@ -389,11 +399,14 @@ class PodmanVolume:
).construct_command_from_params()
full_cmd = " ".join([self.module.params['executable'], 'volume']
+ [to_native(i) for i in b_command])
+ # check if running not from root
+ if os.getuid() != 0 and action == 'mount':
+ full_cmd = f"{self.module.params['executable']} unshare {full_cmd}"
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,
+ full_cmd,
expand_user_and_vars=False)
self.stdout = out
self.stderr = err
@@ -401,6 +414,9 @@ class PodmanVolume:
self.module.fail_json(
msg="Can't %s volume %s" % (action, self.name),
stdout=out, stderr=err)
+ # in case of mount/unmount, return path to the volume from stdout
+ if action in ['mount']:
+ self.mount_point = out.strip()
def delete(self):
"""Delete the volume."""
@@ -410,6 +426,14 @@ class PodmanVolume:
"""Create the volume."""
self._perform_action('create')
+ def mount(self):
+ """Delete the volume."""
+ self._perform_action('mount')
+
+ def unmount(self):
+ """Create the volume."""
+ self._perform_action('unmount')
+
def recreate(self):
"""Recreate the volume."""
self.delete()
@@ -468,6 +492,8 @@ class PodmanVolumeManager:
states_map = {
'present': self.make_present,
'absent': self.make_absent,
+ 'mounted': self.make_mount,
+ 'unmounted': self.make_unmount,
'quadlet': self.make_quadlet,
}
process_action = states_map[self.state]
@@ -501,6 +527,26 @@ class PodmanVolumeManager:
'podman_actions': self.volume.actions})
self.module.exit_json(**self.results)
+ def make_mount(self):
+ """Run actions if desired state is 'mounted'."""
+ if not self.volume.exists:
+ self.volume.create()
+ self.results['actions'].append('created %s' % self.volume.name)
+ self.volume.mount()
+ self.results['actions'].append('mounted %s' % self.volume.name)
+ if self.volume.mount_point:
+ self.results.update({'mount_point': self.volume.mount_point})
+ self.update_volume_result()
+
+ def make_unmount(self):
+ """Run actions if desired state is 'unmounted'."""
+ if self.volume.exists:
+ self.volume.unmount()
+ self.results['actions'].append('unmounted %s' % self.volume.name)
+ self.update_volume_result()
+ else:
+ self.module.fail_json(msg="Volume %s does not exist!" % self.name)
+
def make_quadlet(self):
results_update = create_quadlet_state(self.module, "volume")
self.results.update(results_update)
@@ -511,7 +557,7 @@ def main():
module = AnsibleModule(
argument_spec=dict(
state=dict(type='str', default="present",
- choices=['present', 'absent', 'quadlet']),
+ choices=['present', 'absent', 'mounted', 'unmounted', 'quadlet']),
name=dict(type='str', required=True),
label=dict(type='dict', required=False),
driver=dict(type='str', required=False),