Init: mediaserver

This commit is contained in:
2023-02-08 12:13:28 +01:00
parent 848bc9739c
commit f7c23d4ba9
31914 changed files with 6175775 additions and 0 deletions

View File

@@ -0,0 +1,416 @@
#!/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
import time
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
"""
retry = 0
retry_limit = 4
if not name:
all_names = [executable, 'container', 'ls', '-q', '-a']
rc, out, err = module.run_command(all_names)
# This should not fail in regular circumstances, so retry again
# https://github.com/containers/podman/issues/10225
while rc != 0 and retry <= retry_limit:
module.log(msg="Unable to get list of containers: %s" % err)
time.sleep(1)
retry += 1
rc, out, err = module.run_command(all_names)
if rc != 0:
module.fail_json(msg="Unable to get list of containers during"
" %s retries" % retry_limit)
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()

View File

@@ -0,0 +1,134 @@
#!/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 set_container_opts # noqa: F402
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 = []
for container in module.params['containers']:
options_dict = set_container_opts(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()

View File

@@ -0,0 +1,106 @@
#!/usr/bin/python
# coding: utf-8 -*-
# Copyright (c) 2021, Sagi Shnaidman <sshnaidm@redhat.com>
# 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_export
short_description: Export a podman container
author: Sagi Shnaidman (@sshnaidm)
description:
- podman export exports the filesystem of a container and saves it as a
tarball on the local machine
options:
dest:
description:
- Path to export container to.
type: str
required: true
container:
description:
- Container to export.
type: str
required: true
force:
description:
- Force saving to file even if it exists.
type: bool
default: 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
requirements:
- "Podman installed on host"
'''
RETURN = '''
'''
EXAMPLES = '''
# What modules does for example
- containers.podman.podman_export:
dest: /path/to/tar/file
container: container-name
'''
import os # noqa: E402
from ansible.module_utils.basic import AnsibleModule # noqa: E402
from ..module_utils.podman.common import remove_file_or_dir # noqa: E402
def export(module, executable):
changed = False
command = [executable, 'export']
command += ['-o=%s' % module.params['dest'], module.params['container']]
if module.params['force']:
dest = module.params['dest']
if os.path.exists(dest):
changed = True
if module.check_mode:
return changed, '', ''
try:
remove_file_or_dir(dest)
except Exception as e:
module.fail_json(msg="Error deleting %s path: %s" % (dest, e))
else:
changed = not os.path.exists(module.params['dest'])
if module.check_mode:
return changed, '', ''
rc, out, err = module.run_command(command)
if rc != 0:
module.fail_json(msg="Error exporting container %s: %s" % (
module.params['container'], err))
return changed, out, err
def main():
module = AnsibleModule(
argument_spec=dict(
dest=dict(type='str', required=True),
container=dict(type='str', required=True),
force=dict(type='bool', default=True),
executable=dict(type='str', default='podman')
),
supports_check_mode=True,
)
executable = module.get_bin_path(module.params['executable'], required=True)
changed, out, err = export(module, executable)
results = {
"changed": changed,
"stdout": out,
"stderr": err,
}
module.exit_json(**results)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,572 @@
#!/usr/bin/python
# coding: utf-8 -*-
# 2022, Sébastien Gendre <seb@k-7.ch>
# 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_generate_systemd
author:
- Sébastien Gendre (@CyberFox001)
short_description: Generate systemd unit from a pod or a container
description:
- Generate systemd .service unit file(s) from a pod or a container
- Support Ansible check mode
options:
name:
description:
- Name of the pod or container to export
type: str
required: true
dest:
description:
- Destination of the generated systemd unit file(s)
type: path
new:
description:
- Generate unit files that create containers and pods, not only start them.
- Refer to podman-generate-systemd(1) man page for more information.
type: bool
default: false
restart_policy:
description:
- Restart policy of the service
type: str
choices:
- no-restart
- on-success
- on-failure
- on-abnormal
- on-watchdog
- on-abort
- always
restart_sec:
description:
- Configures the time to sleep before restarting a service (as configured with restart-policy).
- Takes a value in seconds.
- Only with Podman 4.0.0 and above
type: int
start_timeout:
description:
- Override the default start timeout for the container with the given value in seconds.
- Only with Podman 4.0.0 and above
type: int
stop_timeout:
description:
- Override the default stop timeout for the container with the given value in seconds.
type: int
env:
description:
- Set environment variables to the systemd unit files.
- Keys are the environment variable names, and values are the environment variable values
- Only with Podman 4.3.0 and above
type: dict
use_names:
description:
- Use name of the containers for the start, stop, and description in the unit file.
type: bool
default: true
container_prefix:
description:
- Set the systemd unit name prefix for containers.
- If not set, use the default defined by podman, C(container).
- Refer to podman-generate-systemd(1) man page for more information.
type: str
pod_prefix:
description:
- Set the systemd unit name prefix for pods.
- If not set, use the default defined by podman, C(pod).
- Refer to podman-generate-systemd(1) man page for more information.
type: str
separator:
description:
- Systemd unit name separator between the name/id of a container/pod and the prefix.
- If not set, use the default defined by podman, C(-).
- Refer to podman-generate-systemd(1) man page for more information.
type: str
no_header:
description:
- Do not generate the header including meta data such as the Podman version and the timestamp.
type: bool
default: false
after:
description:
- Add the systemd unit after (C(After=)) option, that ordering dependencies between the list of dependencies and this service.
- This option may be specified more than once.
- User-defined dependencies will be appended to the generated unit file
- But any existing options such as needed or defined by default (e.g. C(online.target)) will not be removed or overridden.
- Only with Podman 4.0.0 and above
type: list
elements: str
wants:
description:
- Add the systemd unit wants (C(Wants=)) option, that this service is (weak) dependent on.
- This option may be specified more than once.
- This option does not influence the order in which services are started or stopped.
- User-defined dependencies will be appended to the generated unit file
- But any existing options such as needed or defined by default (e.g. C(online.target)) will not be removed or overridden.
- Only with Podman 4.0.0 and above
type: list
elements: str
requires:
description:
- Set the systemd unit requires (Requires=) option.
- Similar to wants, but declares a stronger requirement dependency.
- Only with Podman 4.0.0 and above
type: list
elements: str
executable:
description:
- C(Podman) executable name or full path
type: str
default: podman
requirements:
- Podman installed on target host
notes:
- You can store your systemd unit files in C(/etc/systemd/user/) for system wide usage
- Or you can store them in C(~/.config/systemd/user/) for usage at a specific user
- If you indicate a pod, the systemd units for it and all its containers will be generated
- Create all your pods, containers and their dependencies before generating the systemd files
- If a container or pod is already started before you do a C(systemctl daemon reload),
systemd will not see the container or pod as started
- Stop your container or pod before you do a C(systemctl daemon reload),
then you can start them with C(systemctl start my_container.service)
'''
EXAMPLES = '''
# Exemple of creating a container and integrate it into systemd
- name: A postgres container must exist, stopped
containers.podman.podman_container:
name: postgres_local
image: docker.io/library/postgres:latest
state: stopped
- name: Systemd unit files for postgres container must exist
containers.podman.podman_generate_systemd:
name: postgres_local
dest: ~/.config/systemd/user/
- name: Postgres container must be started and enabled on systemd
ansible.builtin.systemd:
name: container-postgres_local
daemon_reload: yes
state: started
enabled: yes
# Generate the unit files, but store them on an Ansible variable
# instead of writting them on target host
- name: Systemd unit files for postgres container must be generated
containers.podman.podman_generate_systemd:
name: postgres_local
register: postgres_local_systemd_unit
# Generate the unit files with environment variables sets
- name: Systemd unit files for postgres container must be generated
containers.podman.podman_generate_systemd:
name: postgres_local
env:
POSTGRES_USER: my_app
POSTGRES_PASSWORD: example
register: postgres_local_systemd_unit
'''
RETURN = '''
systemd_units:
description: A copy of the generated systemd .service unit(s)
returned: always
type: dict
sample: {
"container-postgres_local": " #Content of the systemd .servec unit for postgres_local container",
"pod-my_webapp": " #Content of the systemd .servec unit for my_webapp pod",
}
podman_command:
description: A copy of the podman command used to generate the systemd unit(s)
returned: always
type: str
sample: "podman generate systemd my_webapp"
'''
import os
from ansible.module_utils.basic import AnsibleModule
import json
RESTART_POLICY_CHOICES = [
'no-restart',
'on-success',
'on-failure',
'on-abnormal',
'on-watchdog',
'on-abort',
'always',
]
def generate_systemd(module):
'''Generate systemd .service unit file from a pod or container.
Parameter:
- module (AnsibleModule): An AnsibleModule object
Returns (tuple[bool, list[str], str]):
- A boolean which indicate whether the targeted systemd state is modified
- A copy of the generated systemd .service units content
- A copy of the command, as a string
'''
# Flag which indicate whether the targeted system state is modified
changed = False
# Build the podman command, based on the module parameters
command_options = []
# New option
if module.params['new']:
command_options.append('--new')
# Restart policy option
restart_policy = module.params['restart_policy']
if restart_policy:
# add the restart policy to options
if restart_policy == 'no-restart':
restart_policy = 'no'
command_options.append(
'--restart-policy={restart_policy}'.format(
restart_policy=restart_policy,
),
)
# Restart-sec option (only for Podman 4.0.0 and above)
restart_sec = module.params['restart_sec']
if restart_sec:
command_options.append(
'--restart-sec={restart_sec}'.format(
restart_sec=restart_sec,
),
)
# Start-timeout option (only for Podman 4.0.0 and above)
start_timeout = module.params['start_timeout']
if start_timeout:
command_options.append(
'--start-timeout={start_timeout}'.format(
start_timeout=start_timeout,
),
)
# Stop-timeout option
stop_timeout = module.params['stop_timeout']
if stop_timeout:
command_options.append(
'--stop-timeout={stop_timeout}'.format(
stop_timeout=stop_timeout,
),
)
# Use container name(s) option
if module.params['use_names']:
command_options.append('--name')
# Container-prefix option
container_prefix = module.params['container_prefix']
if container_prefix is not None:
command_options.append(
'--container-prefix={container_prefix}'.format(
container_prefix=container_prefix,
),
)
# Pod-prefix option
pod_prefix = module.params['pod_prefix']
if pod_prefix is not None:
command_options.append(
'--pod-prefix={pod_prefix}'.format(
pod_prefix=pod_prefix,
),
)
# Separator option
separator = module.params['separator']
if separator is not None:
command_options.append(
'--separator={separator}'.format(
separator=separator,
),
)
# No-header option
if module.params['no_header']:
command_options.append('--no-header')
# After option (only for Podman 4.0.0 and above)
after = module.params['after']
if after:
for item in after:
command_options.append(
'--after={item}'.format(
item=item,
),
)
# Wants option (only for Podman 4.0.0 and above)
wants = module.params['wants']
if wants:
for item in wants:
command_options.append(
'--wants={item}'.format(
item=item,
)
)
# Requires option (only for Podman 4.0.0 and above)
requires = module.params['requires']
if requires:
for item in requires:
command_options.append(
'--requires={item}'.format(
item=item,
),
)
# Environment variables (only for Podman 4.3.0 and above)
environment_variables = module.params['env']
if environment_variables:
for env_var_name, env_var_value in environment_variables.items():
command_options.append(
"-e='{env_var_name}={env_var_value}'".format(
env_var_name=env_var_name,
env_var_value=env_var_value,
),
)
# Set output format, of podman command, to json
command_options.extend(['--format', 'json'])
# Full command build, with option included
# Base of the command
command = [
module.params['executable'], 'generate', 'systemd',
]
# Add the options to the commande
command.extend(command_options)
# Add pod or container name to the command
command.append(module.params['name'])
# Build the string version of the command, only for module return
command_str = ' '.join(command)
# Run the podman command to generated systemd .service unit(s) content
return_code, stdout, stderr = module.run_command(command)
# In case of error in running the command
if return_code != 0:
# Print informations about the error and return and empty dictionary
message = 'Error generating systemd .service unit(s).'
message += ' Command executed: {command_str}'
message += ' Command returned with code: {return_code}.'
message += ' Error message: {stderr}.'
module.fail_json(
msg=message.format(
command_str=command_str,
return_code=return_code,
stderr=stderr,
),
changed=changed,
systemd_units={},
podman_command=command_str,
)
# In case of command execution success, its stdout is a json
# dictionary. This dictionary is all the generated systemd units.
# Each key value pair is one systemd unit. The key is the unit name
# and the value is the unit content.
# Load the returned json dictionary as a python dictionary
systemd_units = json.loads(stdout)
# Write the systemd .service unit(s) content to file(s), if
# requested
if module.params['dest']:
try:
systemd_units_dest = module.params['dest']
# If destination don't exist
if not os.path.exists(systemd_units_dest):
# If not in check mode, make it
if not module.check_mode:
os.makedirs(systemd_units_dest)
changed = True
# If destination exist but not a directory
if not os.path.isdir(systemd_units_dest):
# Stop and tell user that the destination is not a directry
message = "Destination {systemd_units_dest} is not a directory."
message += " Can't save systemd unit files in."
module.fail_json(
msg=message.format(
systemd_units_dest=systemd_units_dest,
),
changed=changed,
systemd_units=systemd_units,
podman_command=command_str,
)
# Write each systemd unit, if needed
for unit_name, unit_content in systemd_units.items():
# Build full path to unit file
unit_file_name = unit_name + '.service'
unit_file_full_path = os.path.join(
systemd_units_dest,
unit_file_name,
)
# See if we need to write the unit file, default yes
need_to_write_file = True
# If the unit file already exist, compare it with the
# generated content
if os.path.exists(unit_file_full_path):
# Read the file
with open(unit_file_full_path, 'r') as unit_file:
current_unit_file_content = unit_file.read()
# If current unit file content is the same as the
# generated content
if current_unit_file_content == unit_content:
# We don't need to write it
need_to_write_file = False
# Write the file, if needed
if need_to_write_file:
with open(unit_file_full_path, 'w') as unit_file:
# If not in check mode, write the file
if not module.check_mode:
unit_file.write(unit_content)
changed = True
except Exception as exception:
# When exception occurs while trying to write units file
message = 'PODMAN-GENERATE-SYSTEMD-DEBUG: '
message += 'Error writing systemd units files: '
message += '{exception}'
module.log(
message.format(
exception=exception
),
)
# Return the systemd .service unit(s) content
return changed, systemd_units, command_str
def run_module():
'''Run the module on the target'''
# Build the list of parameters user can use
module_parameters = {
'name': {
'type': 'str',
'required': True,
},
'dest': {
'type': 'path',
'required': False,
},
'new': {
'type': 'bool',
'required': False,
'default': False,
},
'restart_policy': {
'type': 'str',
'required': False,
'choices': RESTART_POLICY_CHOICES,
},
'restart_sec': {
'type': 'int',
'required': False,
},
'start_timeout': {
'type': 'int',
'required': False,
},
'stop_timeout': {
'type': 'int',
'required': False,
},
'env': {
'type': 'dict',
'required': False,
},
'use_names': {
'type': 'bool',
'required': False,
'default': True,
},
'container_prefix': {
'type': 'str',
'required': False,
},
'pod_prefix': {
'type': 'str',
'required': False,
},
'separator': {
'type': 'str',
'required': False,
},
'no_header': {
'type': 'bool',
'required': False,
'default': False,
},
'after': {
'type': 'list',
'elements': 'str',
'required': False,
},
'wants': {
'type': 'list',
'elements': 'str',
'required': False,
},
'requires': {
'type': 'list',
'elements': 'str',
'required': False,
},
'executable': {
'type': 'str',
'required': False,
'default': 'podman',
},
}
# Build result dictionary
result = {
'changed': False,
'systemd_units': {},
'podman_command': '',
}
# Build the Ansible Module
module = AnsibleModule(
argument_spec=module_parameters,
supports_check_mode=True
)
# Generate the systemd units
state_changed, systemd_units, podman_command = generate_systemd(module)
result['changed'] = state_changed
result['systemd_units'] = systemd_units
result['podman_command'] = podman_command
# Return the result
module.exit_json(**result)
def main():
'''Main function of this script.'''
run_module()
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,837 @@
#!/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 the build context directory.
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.
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
default: {}
aliases:
- build_args
- buildargs
suboptions:
file:
description:
- Path to the Containerfile if it is not in the build context directory.
type: path
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
default: {}
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
containers.podman.podman_image:
name: quay.io/bitnami/wildfly
- name: Remove an image
containers.podman.podman_image:
name: quay.io/bitnami/wildfly
state: absent
- name: Remove an image with image id
containers.podman.podman_image:
name: 0e901e68141f
state: absent
- name: Pull a specific version of an image
containers.podman.podman_image:
name: redis
tag: 4
- name: Build a basic OCI image
containers.podman.podman_image:
name: nginx
path: /path/to/build/dir
- name: Build a basic OCI image with advanced parameters
containers.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
extra_args: "--build-arg KEY=value"
- name: Build a Docker formatted image
containers.podman.podman_image:
name: nginx
path: /path/to/build/dir
build:
format: docker
- name: Build and push an image using existing credentials
containers.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
containers.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
containers.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
containers.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
containers.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._text import to_native
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):
cmd = " ".join([self.executable]
+ [to_native(i) for i in args])
self.module.log("PODMAN-IMAGE-DEBUG: %s" % cmd)
self.results['podman_actions'].append(cmd)
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()
if not self.module.check_mode:
digest_after = image[0].get('Digest', image[0].get('digest'))
self.results['changed'] = digest_before != digest_after
else:
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'] = self.push_image()
def absent(self):
image = self.find_image()
image_id = self.find_image_id()
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()
elif image_id:
self.results['actions'].append(
'Removed image with id {id}'.format(id=self.image_name))
self.results['changed'] = True
self.results['image']['state'] = 'Deleted'
if not self.module.check_mode:
self.remove_image_id()
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 find_image_id(self, image_id=None):
if image_id is None:
# If image id is set as image_name, remove tag
image_id = re.sub(':.*$', '', self.image_name)
args = ['image', 'ls', '--quiet', '--no-trunc']
rc, candidates, err = self._run(args, ignore_errors=True)
candidates = [re.sub('^sha256:', '', c)
for c in str.splitlines(candidates)]
for c in candidates:
if c.startswith(image_id):
return image_id
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:
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))
else:
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')
containerfile = self.build.get('file')
if containerfile:
args.extend(['--file', containerfile])
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(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 remove_image_id(self, image_id=None):
if image_id is None:
image_id = re.sub(':.*$', '', self.image_name)
args = ['rmi', image_id]
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 with id {image_id}. {err}'.format(
image_id=image_id, 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', 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),
file=dict(type='path'),
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=(
['auth_file', 'username'],
['auth_file', 'password'],
),
)
results = dict(
changed=False,
actions=[],
podman_actions=[],
image={},
)
PodmanImageManager(module, results)
module.exit_json(**results)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,236 @@
#!/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)
out = out.strip()
if out:
name = out.split('\n')
res = get_image_info(module, executable, name)
return res
return json.dumps([])
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()

View File

@@ -0,0 +1,157 @@
#!/usr/bin/python
# coding: utf-8 -*-
# Copyright (c) 2021, Sagi Shnaidman <sshnaidm@redhat.com>
# 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_import
short_description: Import Podman container from a tar file.
author: Sagi Shnaidman (@sshnaidm)
description:
- podman import imports a tarball (.tar, .tar.gz, .tgz, .bzip, .tar.xz, .txz)
and saves it as a filesystem image.
options:
src:
description:
- Path to image file to load.
type: str
required: true
commit_message:
description:
- Set commit message for imported image
type: str
change:
description:
- Set changes as list of key-value pairs, see example.
type: list
elements: dict
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
requirements:
- "Podman installed on host"
'''
RETURN = '''
image:
description: info from loaded image
returned: always
type: dict
sample: {
"Id": "cbc6d73c4d232db6e8441df96af81855f62c74157b5db80a1d5...",
"Digest": "sha256:8730c75be86a718929a658db4663d487e562d66762....",
"RepoTags": [],
"RepoDigests": [],
"Parent": "",
"Comment": "imported from tarball",
"Created": "2021-09-07T04:45:38.749977105+03:00",
"Config": {},
"Version": "",
"Author": "",
"Architecture": "amd64",
"Os": "linux",
"Size": 5882449,
"VirtualSize": 5882449,
"GraphDriver": {
"Name": "overlay",
"Data": {
"UpperDir": "/home/...34/diff",
"WorkDir": "/home/.../work"
}
},
"RootFS": {
"Type": "layers",
"Layers": [
"sha256:...."
]
},
"Labels": null,
"Annotations": {},
"ManifestType": "application/vnd.oci.image.manifest.v1+json",
"User": "",
"History": [
{
"created": "2021-09-07T04:45:38.749977105+03:00",
"created_by": "/bin/sh -c #(nop) ADD file:091... in /",
"comment": "imported from tarball"
}
],
"NamesHistory": null
}
'''
EXAMPLES = '''
# What modules does for example
- containers.podman.podman_import:
src: /path/to/tar/file
change:
- "CMD": /bin/bash
- "User": root
commit_message: "Importing image"
'''
import json # noqa: E402
from ansible.module_utils.basic import AnsibleModule # noqa: E402
def load(module, executable):
changed = False
command = [executable, 'import']
if module.params['commit_message']:
command.extend(['--message', module.params['commit_message']])
if module.params['change']:
for change in module.params['change']:
command += ['--change', "=".join(list(change.items())[0])]
command += [module.params['src']]
changed = True
if module.check_mode:
return changed, '', '', '', command
rc, out, err = module.run_command(command)
if rc != 0:
module.fail_json(msg="Image loading failed: %s" % (err))
image_name_line = [i for i in out.splitlines() if 'sha256' in i][0]
image_name = image_name_line.split(":", maxsplit=1)[1].strip()
rc, out2, err2 = module.run_command([executable, 'image', 'inspect', image_name])
if rc != 0:
module.fail_json(msg="Image %s inspection failed: %s" % (image_name, err2))
try:
info = json.loads(out2)[0]
except Exception as e:
module.fail_json(msg="Could not parse JSON from image %s: %s" % (image_name, e))
return changed, out, err, info, command
def main():
module = AnsibleModule(
argument_spec=dict(
src=dict(type='str', required=True),
commit_message=dict(type='str'),
change=dict(type='list', elements='dict'),
executable=dict(type='str', default='podman')
),
supports_check_mode=True,
)
executable = module.get_bin_path(module.params['executable'], required=True)
changed, out, err, image_info, command = load(module, executable)
results = {
"changed": changed,
"stdout": out,
"stderr": err,
"image": image_info,
"podman_command": " ".join(command)
}
module.exit_json(**results)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,199 @@
#!/usr/bin/python
# coding: utf-8 -*-
# Copyright (c) 2020, Sagi Shnaidman <sshnaidm@redhat.com>
# 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_load
short_description: Load image from a tar file.
author: Sagi Shnaidman (@sshnaidm)
description:
- podman load loads an image from either an oci-archive or a docker-archive stored
on the local machine into container storage.
podman load is used for loading from the archive generated by podman save,
that includes the image parent layers.
options:
input:
description:
- Path to image file to load.
type: str
required: true
aliases:
- 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
requirements:
- "Podman installed on host"
'''
RETURN = '''
image:
description: info from loaded image
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
}
]
'''
EXAMPLES = '''
# What modules does for example
- containers.podman.podman_load:
input: /path/to/tar/file
'''
import json # noqa: E402
from ansible.module_utils.basic import AnsibleModule # noqa: E402
def load(module, executable):
changed = False
command = [executable, 'load', '--input']
command.append(module.params['input'])
changed = True
if module.check_mode:
return changed, '', '', ''
rc, out, err = module.run_command(command)
if rc != 0:
module.fail_json(msg="Image loading failed: %s" % (err))
image_name_line = [i for i in out.splitlines() if 'Loaded image' in i][0]
# For Podman < 4.x
if 'Loaded image(s):' in image_name_line:
image_name = image_name_line.split("Loaded image(s): ")[1].split(',')[0].strip()
# For Podman > 4.x
elif 'Loaded image:' in image_name_line:
image_name = image_name_line.split("Loaded image: ")[1].strip()
else:
module.fail_json(msg="Not found images in %s" % image_name_line)
rc, out2, err2 = module.run_command([executable, 'image', 'inspect', image_name])
if rc != 0:
module.fail_json(msg="Image %s inspection failed: %s" % (image_name, err2))
try:
info = json.loads(out2)[0]
except Exception as e:
module.fail_json(msg="Could not parse JSON from image %s: %s" % (image_name, e))
return changed, out, err, info
def main():
module = AnsibleModule(
argument_spec=dict(
input=dict(type='str', required=True, aliases=['path']),
executable=dict(type='str', default='podman')
),
supports_check_mode=True,
)
executable = module.get_bin_path(module.params['executable'], required=True)
changed, out, err, image_info = load(module, executable)
results = {
"changed": changed,
"stdout": out,
"stderr": err,
"image": image_info,
}
module.exit_json(**results)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,186 @@
#!/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
author:
- "Jason Hiatt (@jthiatt)"
- "Clemens Lange (@clelange)"
short_description: Login to a container registry using podman
notes: []
description:
- Login to a container registry server using the podman login command
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`.
requirements:
- "Podman installed on host"
options:
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
certdir:
description:
- Use certificates at path (*.crt, *.cert, *.key) to connect
to the registry. Default certificates directory
is /etc/containers/certs.d.
type: path
password:
description:
- Password for the registry server.
required: True
type: str
registry:
description:
- Registry server. If the registry is not specified,
the first registry under `[registries.search]` from
`registries.conf` will be used.
type: str
tlsverify:
description:
- Require HTTPS and verify certificates when
contacting registries. If explicitly set to true,
then TLS verification will be used. If set to false,
then TLS verification will not be used. If not specified,
TLS verification will be used unless the target registry
is listed as an insecure registry in registries.conf.
type: bool
username:
description:
- Username for the registry server.
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
'''
EXAMPLES = r"""
- name: Login to default registry and create ${XDG_RUNTIME_DIR}/containers/auth.json
containers.podman.podman_login:
username: user
password: 'p4ssw0rd'
- name: Login to default registry and create ${XDG_RUNTIME_DIR}/containers/auth.json
containers.podman.podman_login:
username: user
password: 'p4ssw0rd'
registry: quay.io
"""
# noqa: F402
import json # noqa: F402
import hashlib
import os
from ansible.module_utils.basic import AnsibleModule
def login(module, executable, registry, authfile,
certdir, tlsverify, username, password):
command = [executable, 'login']
changed = False
if username:
command.extend(['--username', username])
if password:
command.extend(['--password', password])
if authfile:
command.extend(['--authfile', authfile])
authfile = os.path.expandvars(authfile)
else:
authfile = os.getenv('XDG_RUNTIME_DIR', '') + '/containers/auth.json'
if registry:
command.append(registry)
if certdir:
command.extend(['--cert-dir', certdir])
if tlsverify is not None:
if tlsverify:
command.append('--tls-verify')
else:
command.append('--tls-verify=False')
# Use a checksum to check if the auth JSON has changed
checksum = None
docker_authfile = os.path.expandvars('$HOME/.docker/config.json')
# podman falls back to ~/.docker/config.json if the default authfile doesn't exist
check_file = authfile if os.path.exists(authfile) else docker_authfile
if os.path.exists(check_file):
content = open(check_file, 'rb').read()
if bytes(registry, 'UTF-8') in content:
checksum = hashlib.md5(content).hexdigest()
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 login
changed = True
if 'Existing credentials are valid' in out:
changed = False
# If we have managed to calculate a checksum before, check if it has changed
# due to the login
if checksum:
content = open(check_file, 'rb').read()
if bytes(registry, 'UTF-8') in content:
new_checksum = hashlib.md5(content).hexdigest()
if new_checksum == checksum:
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'),
username=dict(type='str', required=True),
password=dict(type='str', required=True, no_log=True),
certdir=dict(type='path'),
tlsverify=dict(type='bool'),
),
supports_check_mode=True,
required_together=(
['username', 'password'],
),
mutually_exclusive=(
['certdir', 'tlsverify'],
),
)
registry = module.params['registry']
authfile = module.params['authfile']
username = module.params['username']
password = module.params['password']
certdir = module.params['certdir']
tlsverify = module.params['tlsverify']
executable = module.get_bin_path(module.params['executable'], required=True)
changed, out, err = login(module, executable, registry, authfile,
certdir, tlsverify, username, password)
results = {
"changed": changed,
"stdout": out,
"stderr": err,
}
module.exit_json(**results)
if __name__ == '__main__':
main()

View File

@@ -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()

View File

@@ -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()

View File

@@ -0,0 +1,645 @@
#!/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
import os # noqa: F402
try:
import ipaddress
HAS_IP_ADDRESS_MODULE = True
except ImportError:
HAS_IP_ADDRESS_MODULE = False
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 LooseVersion
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=%s' % self.params['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():
if opt[1] is not None:
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,
'ipv6': 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):
# For v3 it's impossible to find out DNS settings.
if LooseVersion(self.version) >= LooseVersion('4.0.0'):
before = not self.info.get('dns_enabled', True)
after = self.params['disable_dns']
return self._diff_update_and_compare('disable_dns', before, after)
before = after = self.params['disable_dns']
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_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)
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:
after = self.params['gateway']
return self._diff_update_and_compare('gateway', before, after)
def diffparam_internal(self):
if LooseVersion(self.version) >= LooseVersion('4.0.0'):
before = self.info.get('internal', False)
after = self.params['internal']
return self._diff_update_and_compare('internal', before, after)
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):
# 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):
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)
def diffparam_macvlan(self):
before = after = ''
return self._diff_update_and_compare('macvlan', before, after)
def diffparam_opt(self):
if LooseVersion(self.version) >= LooseVersion('4.0.0'):
vlan_before = self.info.get('options', {}).get('vlan')
else:
try:
vlan_before = self.info['plugins'][0].get('vlan')
except (IndexError, KeyError):
vlan_before = None
vlan_after = self.params['opt'].get('vlan') if self.params['opt'] else None
if vlan_before or vlan_after:
before, after = {'vlan': str(vlan_before)}, {'vlan': str(vlan_after)}
else:
before, after = {}, {}
if LooseVersion(self.version) >= LooseVersion('4.0.0'):
mtu_before = self.info.get('options', {}).get('mtu')
else:
try:
mtu_before = self.info['plugins'][0].get('mtu')
except (IndexError, KeyError):
mtu_before = None
mtu_after = self.params['opt'].get('mtu') if self.params['opt'] else None
if mtu_before or mtu_after:
before.update({'mtu': str(mtu_before)})
after.update({'mtu': str(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()

View File

@@ -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()

View File

@@ -0,0 +1,300 @@
#!/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_play
author:
- "Sagi Shnaidman (@sshnaidm)"
short_description: Play kubernetes YAML file using podman
notes: []
description:
- The module reads in a structured file of Kubernetes YAML.
It will then recreate the pod and containers described in the YAML.
requirements:
- "Podman installed on host"
options:
executable:
description:
- Name of executable to run, by default 'podman'
type: str
default: podman
kube_file:
description:
- Path to file with YAML configuration for a Pod.
type: path
required: True
authfile:
description:
- Path of the authentication file. Default is ${XDG_RUNTIME_DIR}/containers/auth.json,
which is set using podman login. If the authorization state is not found there,
$HOME/.docker/config.json is checked, which is set using docker login.
Note - 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
cert_dir:
description:
- Use certificates at path (*.crt, *.cert, *.key) to connect to the registry.
Default certificates directory is /etc/containers/certs.d.
(This option is not available with the remote Podman client)
type: path
configmap:
description:
- Use Kubernetes configmap YAML at path to provide a source for environment
variable values within the containers of the pod.
Note - The configmap option can be used multiple times to pass multiple
Kubernetes configmap YAMLs
type: list
elements: path
seccomp_profile_root:
description:
- Directory path for seccomp profiles (default is "/var/lib/kubelet/seccomp").
This option is not available with the remote Podman client
type: path
username:
description:
- The username and password to use to authenticate with the registry if required.
type: str
password:
description:
- The username and password to use to authenticate with the registry if required.
type: str
log_driver:
description:
- Set logging driver for all created containers.
type: str
log_level:
description:
- Set logging level for podman calls. Log messages above specified level
("debug"|"info"|"warn"|"error"|"fatal"|"panic") (default "error")
type: str
choices:
- debug
- info
- warn
- error
- fatal
- panic
network:
description:
- List of the names of CNI networks the pod should join.
type: list
elements: str
state:
description:
- Start the pod after creating it, or to leave it created only.
type: str
choices:
- created
- started
- absent
required: True
tls_verify:
description:
- Require HTTPS and verify certificates when contacting registries (default is true).
If explicitly set to true, then TLS verification will be used. If set to false,
then TLS verification will not be used. If not specified, TLS verification will be
used unless the target registry is listed as an insecure registry in registries.conf.
type: bool
debug:
description:
- Enable debug for the module.
type: bool
recreate:
description:
- If pod already exists, delete it and run the new one.
type: bool
quiet:
description:
- Hide image pulls logs from output.
type: bool
'''
EXAMPLES = '''
- name: Play kube file
containers.podman.podman_play:
kube_file: ~/kube.yaml
state: started
'''
import re # noqa: F402
try:
import yaml
HAS_YAML = True
except ImportError:
HAS_YAML = False
from ansible.module_utils.basic import AnsibleModule # noqa: F402
NAME = re.compile('name "([^"]+)" is in use')
class PodmanKubeManagement:
def __init__(self, module, executable):
self.module = module
self.actions = []
self.executable = executable
self.command = [self.executable, 'play', 'kube']
creds = []
# pod_name = extract_pod_name(module.params['kube_file'])
if self.module.params['username']:
creds += [self.module.params['username']]
if self.module.params['password']:
creds += [self.module.params['password']]
creds = ":".join(creds)
self.command.extend(['--creds=%s' % creds])
if self.module.params['network']:
networks = ",".join(self.module.params['network'])
self.command.extend(['--network=%s' % networks])
if self.module.params['configmap']:
configmaps = ",".join(self.module.params['configmap'])
self.command.extend(['--configmap=%s' % configmaps])
start = self.module.params['state'] == 'started'
self.command.extend(['--start=%s' % str(start).lower()])
for arg, param in {
'--authfile': 'authfile',
'--cert-dir': 'cert_dir',
'--log-driver': 'log_driver',
'--seccomp-profile-root': 'seccomp_profile_root',
'--tls-verify': 'tls_verify',
'--log-level': 'log_level',
'--quiet': 'quiet',
}.items():
if self.module.params[param] is not None:
self.command += ["%s=%s" % (arg, self.module.params[param])]
self.command += [self.module.params['kube_file']]
def _command_run(self, cmd):
rc, out, err = self.module.run_command(cmd)
self.actions.append(" ".join(cmd))
if self.module.params['debug']:
self.module.log('PODMAN-PLAY-KUBE command: %s' % " ".join(cmd))
self.module.log('PODMAN-PLAY-KUBE stdout: %s' % out)
self.module.log('PODMAN-PLAY-KUBE stderr: %s' % err)
self.module.log('PODMAN-PLAY-KUBE rc: %s' % rc)
return rc, out, err
def discover_pods(self):
pod_name = ''
if self.module.params['kube_file']:
if HAS_YAML:
with open(self.module.params['kube_file']) as f:
pod = yaml.safe_load(f)
if 'metadata' in pod:
pod_name = pod['metadata'].get('name')
else:
self.module.fail_json(
"No metadata in Kube file!\n%s" % pod)
else:
with open(self.module.params['kube_file']) as text:
re_pod = NAME.search(text.read())
if re_pod:
pod_name = re_pod.group(1)
if not pod_name:
self.module.fail_json("Deployment doesn't have a name!")
# Find all pods
all_pods = ''
# In case of one pod or replicasets
for name in ("name=%s$", "name=%s-pod-*"):
cmd = [self.executable,
"pod", "ps", "-q", "--filter", name % pod_name]
rc, out, err = self._command_run(cmd)
all_pods += out
ids = list(set([i for i in all_pods.splitlines() if i]))
return ids
def remove_associated_pods(self, pods):
changed = False
out_all, err_all = '', ''
# Delete all pods
for pod_id in pods:
rc, out, err = self._command_run(
[self.executable, "pod", "rm", "-f", pod_id])
if rc != 0:
self.module.fail_json("Can NOT delete Pod %s" % pod_id)
else:
changed = True
out_all += out
err_all += err
return changed, out_all, err_all
def pod_recreate(self):
pods = self.discover_pods()
self.remove_associated_pods(pods)
# Create a pod
rc, out, err = self._command_run(self.command)
if rc != 0:
self.module.fail_json("Can NOT create Pod! Error: %s" % err)
return out, err
def play(self):
rc, out, err = self._command_run(self.command)
if rc != 0 and 'pod already exists' in err:
if self.module.params['recreate']:
out, err = self.pod_recreate()
changed = True
else:
changed = False
err = "\n".join([
i for i in err.splitlines() if 'pod already exists' not in i])
elif rc != 0:
self.module.fail_json(msg="Output: %s\nError=%s" % (out, err))
else:
changed = True
return changed, out, err
def main():
module = AnsibleModule(
argument_spec=dict(
executable=dict(type='str', default='podman'),
kube_file=dict(type='path', required=True),
authfile=dict(type='path'),
cert_dir=dict(type='path'),
configmap=dict(type='list', elements='path'),
seccomp_profile_root=dict(type='path'),
username=dict(type='str'),
password=dict(type='str', no_log=True),
log_driver=dict(type='str'),
network=dict(type='list', elements='str'),
state=dict(
type='str',
choices=['started', 'created', 'absent'],
required=True),
tls_verify=dict(type='bool'),
debug=dict(type='bool'),
quiet=dict(type='bool'),
recreate=dict(type='bool'),
log_level=dict(
type='str',
choices=["debug", "info", "warn", "error", "fatal", "panic"]),
),
supports_check_mode=True,
)
executable = module.get_bin_path(
module.params['executable'], required=True)
manage = PodmanKubeManagement(module, executable)
if module.params['state'] == 'absent':
pods = manage.discover_pods()
changed, out, err = manage.remove_associated_pods(pods)
else:
changed, out, err = manage.play()
results = {
"changed": changed,
"stdout": out,
"stderr": err,
"actions": manage.actions
}
module.exit_json(**results)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,415 @@
#!/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
cpus:
description:
- Set the total number of CPUs delegated to the pod.
Default is 0.000 which indicates that there is no limit on computation power.
required: false
type: str
cpuset_cpus:
description:
- Limit the CPUs to support execution. First CPU is numbered 0.
Unlike `cpus` this is of type string and parsed as a list of numbers. Format is 0-3,0,1
required: false
type: str
device:
description:
- Add a host device to the pod. Optional permissions parameter can be used to specify
device permissions. It is a combination of r for read, w for write, and m for mknod(2)
elements: str
required: false
type: list
device_read_bps:
description:
- Limit read rate (bytes per second) from a device (e.g. device-read-bps=/dev/sda:1mb)
elements: str
required: false
type: list
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
generate_systemd:
description:
- Generate systemd unit file for container.
type: dict
default: {}
suboptions:
path:
description:
- Specify a path to the directory where unit files will be generated.
Required for this option. If it doesn't exist, the directory will be created.
type: str
required: false
restart_policy:
description:
- Specify a restart policy for the service. The restart-policy must be one of
"no", "on-success", "on-failure", "on-abnormal", "on-watchdog", "on-abort", or "always".
The default policy is "on-failure".
type: str
required: false
choices:
- 'no'
- 'on-success'
- 'on-failure'
- 'on-abnormal'
- 'on-watchdog'
- 'on-abort'
- 'always'
time:
description:
- Override the default stop timeout for the container with the given value.
type: int
required: false
no_header:
description:
- Do not generate the header including meta data such as the Podman version and the timestamp.
From podman version 3.1.0.
type: bool
default: false
names:
description:
- Use names of the containers for the start, stop, and description in the unit file.
Default is true.
type: bool
default: true
container_prefix:
description:
- Set the systemd unit name prefix for containers. The default is "container".
type: str
required: false
pod_prefix:
description:
- Set the systemd unit name prefix for pods. The default is "pod".
type: str
required: false
separator:
description:
- Set the systemd unit name separator between the name/id of a
container/pod and the prefix. The default is "-" (dash).
type: str
required: false
new:
description:
- Create containers and pods when the unit is started instead of
expecting them to exist. The default is "false".
Refer to podman-generate-systemd(1) for more information.
type: bool
default: false
after:
type: list
elements: str
required: false
description:
- Add the systemd unit after (After=) option, that ordering dependencies between the list of dependencies and this service.
wants:
type: list
elements: str
required: false
description:
- Add the systemd unit wants (Wants=) option, that this service is (weak) dependent on.
requires:
type: list
elements: str
required: false
description:
- Set the systemd unit requires (Requires=) option. Similar to wants, but declares a stronger requirement dependency.
gidmap:
description:
- GID map for the user namespace. Using this flag will run the container with
user namespace enabled. It conflicts with the `userns` and `subgidname` flags.
elements: str
required: false
type: list
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
infra_name:
description:
- The name that will be used for the pod's infra container.
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 list of names of CNI networks to join.
type: list
elements: str
required: false
network_alias:
description:
- Add a network-scoped alias for the pod, setting the alias for all networks that the pod joins.
To set a name only for a specific network, use the alias option as described under the -`network` option.
Network aliases work only with the bridge networking mode.
This option can be specified multiple times.
elements: str
required: false
type: list
aliases:
- network_aliases
no_hosts:
description:
- Disable creation of /etc/hosts for the pod.
type: bool
required: false
pid:
description:
- Set the PID mode for the pod. The default is to create a private PID namespace
for the pod. Requires the PID namespace to be shared via `share` option.
required: false
type: str
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
subgidname:
description:
- Name for GID map from the /etc/subgid file. Using this flag will run the container
with user namespace enabled. This flag conflicts with `userns` and `gidmap`.
required: false
type: str
subuidname:
description:
- Name for UID map from the /etc/subuid file.
Using this flag will run the container with user namespace enabled.
This flag conflicts with `userns` and `uidmap`.
required: false
type: str
uidmap:
description:
- Run the container in a new user namespace using the supplied mapping.
This option conflicts with the `userns` and `subuidname` options.
This option provides a way to map host UIDs to container UIDs.
It can be passed several times to map different ranges.
elements: str
required: false
type: list
userns:
description:
- Set the user namespace mode for all the containers in a pod.
It defaults to the PODMAN_USERNS environment variable.
An empty value ("") means user namespaces are disabled.
required: false
type: str
volume:
description:
- Create a bind mount.
aliases:
- volumes
elements: str
required: false
type: list
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"
# Connect random port from localhost to port 80 on pod2
- name: Connect random port from localhost to port 80 on pod2
containers.podman.podman_pod:
name: pod2
state: started
publish: "127.0.0.1::80"
'''
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()

View File

@@ -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 [], [err], [rc]
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()

View File

@@ -0,0 +1,145 @@
#!/usr/bin/python
# coding: utf-8 -*-
# Copyright (c) 2020, Sagi Shnaidman <sshnaidm@redhat.com>
# 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_save
short_description: Saves podman image to tar file
author: Sagi Shnaidman (@sshnaidm)
description:
- podman save saves an image to either docker-archive, oci-archive, oci-dir
(directory with oci manifest type), or docker-dir (directory with v2s2 manifest type)
on the local machine, default is docker-archive.
options:
image:
description:
- Image to save.
type: str
required: true
compress:
description:
- Compress tarball image layers when pushing to a directory using the 'dir' transport.
(default is same compression type, compressed or uncompressed, as source)
type: bool
dest:
description:
- Destination file to write image to.
type: str
required: true
aliases:
- path
format:
description:
- Save image to docker-archive, oci-archive (see containers-transports(5)), oci-dir
(oci transport), or docker-dir (dir transport with v2s2 manifest type).
type: str
choices:
- docker-archive
- oci-archive
- oci-dir
- docker-dir
multi_image_archive:
description:
- Allow for creating archives with more than one image. Additional names will be
interpreted as images instead of tags. Only supported for docker-archive.
type: bool
force:
description:
- Force saving to file even if it exists.
type: bool
default: 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
requirements:
- "Podman installed on host"
'''
RETURN = '''
'''
EXAMPLES = '''
# What modules does for example
- containers.podman.podman_save:
dest: /path/to/tar/file
compress: true
format: oci-dir
'''
import os # noqa: E402
from ansible.module_utils.basic import AnsibleModule # noqa: E402
from ..module_utils.podman.common import remove_file_or_dir # noqa: E402
def save(module, executable):
changed = False
command = [executable, 'save']
cmd_args = {
'compress': ['--compress'],
'dest': ['-o=%s' % module.params['dest']],
'format': ['--format=%s' % module.params['format']],
'multi_image_archive': ['--multi-image-archive'],
}
for param in module.params:
if module.params[param] is not None and param in cmd_args:
command += cmd_args[param]
command.append(module.params['image'])
if module.params['force']:
dest = module.params['dest']
if os.path.exists(dest):
changed = True
if module.check_mode:
return changed, '', ''
try:
remove_file_or_dir(dest)
except Exception as e:
module.fail_json(msg="Error deleting %s path: %s" % (dest, e))
else:
changed = not os.path.exists(module.params['dest'])
if module.check_mode:
return changed, '', ''
rc, out, err = module.run_command(command)
if rc != 0:
module.fail_json(msg="Error: %s" % (err))
return changed, out, err
def main():
module = AnsibleModule(
argument_spec=dict(
image=dict(type='str', required=True),
compress=dict(type='bool'),
dest=dict(type='str', required=True, aliases=['path']),
format=dict(type='str', choices=['docker-archive', 'oci-archive', 'oci-dir', 'docker-dir']),
multi_image_archive=dict(type='bool'),
force=dict(type='bool', default=True),
executable=dict(type='str', default='podman')
),
supports_check_mode=True,
)
if module.params['compress'] and module.params['format'] not in ['oci-dir', 'docker-dir']:
module.fail_json(msg="Compression is only supported for oci-dir and docker-dir format")
executable = module.get_bin_path(module.params['executable'], required=True)
changed, out, err = save(module, executable)
results = {
"changed": changed,
"stdout": out,
"stderr": err,
}
module.exit_json(**results)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,178 @@
#!/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_secret
author:
- "Aliaksandr Mianzhynski (@amenzhinsky)"
version_added: '1.7.0'
short_description: Manage podman secrets
notes: []
description:
- Manage podman secrets
requirements:
- podman
options:
data:
description:
- The value of the secret. Required when C(state) is C(present).
type: str
driver:
description:
- Override default secrets driver, currently podman uses C(file)
which is unencrypted.
type: str
driver_opts:
description:
- Driver-specific key-value options.
type: dict
executable:
description:
- Path to C(podman) executable if it is not in the C($PATH) on the
machine running C(podman)
type: str
default: 'podman'
force:
description:
- Use it when C(state) is C(present) to remove and recreate an existing secret.
type: bool
default: false
skip_existing:
description:
- Use it when C(state) is C(present) and secret with the same name already exists.
If set to C(true), the secret will NOT be recreated and remains as is.
type: bool
default: false
name:
description:
- The name of the secret.
required: True
type: str
state:
description:
- Whether to create or remove the named secret.
type: str
default: present
choices:
- absent
- present
'''
EXAMPLES = r"""
- name: Create secret
containers.podman.podman_secret:
state: present
name: mysecret
data: "my super secret content"
- name: Create container that uses the secret
containers.podman.podman_container:
name: showmysecret
image: docker.io/alpine:3.14
secrets:
- mysecret
detach: false
command: cat /run/secrets/mysecret
register: container
- name: Output secret data
debug:
msg: '{{ container.stdout }}'
- name: Remove secret
containers.podman.podman_secret:
state: absent
name: mysecret
"""
from ansible.module_utils.basic import AnsibleModule
def podman_secret_create(module, executable, name, data, force, skip,
driver, driver_opts):
if force:
module.run_command([executable, 'secret', 'rm', name])
if skip:
rc, out, err = module.run_command(
[executable, 'secret', 'ls', "--format", "{{.Name}}"])
if name in [i.strip() for i in out.splitlines()]:
return {
"changed": False,
}
cmd = [executable, 'secret', 'create']
if driver:
cmd.append('--driver')
cmd.append(driver)
if driver_opts:
cmd.append('--driver-opts')
cmd.append(",".join("=".join(i) for i in driver_opts.items()))
cmd.append(name)
cmd.append('-')
rc, out, err = module.run_command(cmd, data=data, binary_data=True)
if rc != 0:
module.fail_json(msg="Unable to create secret: %s" % err)
return {
"changed": True,
}
def podman_secret_remove(module, executable, name):
changed = False
rc, out, err = module.run_command([executable, 'secret', 'rm', name])
if rc == 0:
changed = True
elif 'no such secret' in err:
pass
else:
module.fail_json(msg="Unable to remove secret: %s" % err)
return {
"changed": changed,
}
def main():
module = AnsibleModule(
argument_spec=dict(
executable=dict(type='str', default='podman'),
state=dict(type='str', default='present', choices=['absent', 'present']),
name=dict(type='str', required=True),
data=dict(type='str', no_log=True),
force=dict(type='bool', default=False),
skip_existing=dict(type='bool', default=False),
driver=dict(type='str'),
driver_opts=dict(type='dict'),
),
)
state = module.params['state']
name = module.params['name']
executable = module.get_bin_path(module.params['executable'], required=True)
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']
results = podman_secret_create(module, executable,
name, data, force, skip,
driver, driver_opts)
else:
results = podman_secret_remove(module, executable, name)
module.exit_json(**results)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,91 @@
#!/usr/bin/python
# coding: utf-8 -*-
# Copyright (c) 2021, Christian Bourque <@ocafebabe>
# 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_tag
short_description: Add an additional name to a local image
author: Christian Bourque (@ocafebabe)
description:
- podman tag adds one or more additional names to locally-stored image.
options:
image:
description:
- Image to tag.
type: str
required: true
target_names:
description:
- Additional names.
type: list
elements: 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
requirements:
- "Podman installed on host"
'''
RETURN = '''
'''
EXAMPLES = '''
# What modules does for example
- containers.podman.podman_tag:
image: docker.io/continuumio/miniconda3
target_names:
- miniconda3
- miniconda
'''
from ansible.module_utils.basic import AnsibleModule # noqa: E402
def tag(module, executable):
changed = False
command = [executable, 'tag']
command.append(module.params['image'])
command.extend(module.params['target_names'])
if module.check_mode:
return changed, '', ''
rc, out, err = module.run_command(command)
if rc == 0:
changed = True
else:
module.fail_json(msg="Error tagging local image %s: %s" % (
module.params['image'], err))
return changed, out, err
def main():
module = AnsibleModule(
argument_spec=dict(
image=dict(type='str', required=True),
target_names=dict(type='list', elements='str', required=True),
executable=dict(type='str', default='podman')
),
supports_check_mode=True,
)
executable = module.get_bin_path(module.params['executable'], required=True)
changed, out, err = tag(module, executable)
results = {
"changed": changed,
"stdout": out,
"stderr": err,
}
module.exit_json(**results)
if __name__ == '__main__':
main()

View File

@@ -0,0 +1,484 @@
#!/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'.
UID and GID idempotency is not supported due to changes in podman.
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 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 LooseVersion
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 {}
# Removing GID and UID from options list
before.pop('uid', None)
before.pop('gid', None)
# Collecting all other options in the list
before = ["=".join((k, v)) for k, v in before.items()]
after = self.params['options']
# # For UID, GID
# if 'uid' in self.info or 'gid' in self.info:
# ids = []
# if 'uid' in self.info and self.info['uid']:
# before = [i for i in before if 'uid' not in i]
# before += ['uid=%s' % str(self.info['uid'])]
# if 'gid' in self.info and self.info['gid']:
# before = [i for i in before if 'gid' not in i]
# 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()

View File

@@ -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()