diff options
Diffstat (limited to 'collections-debian-merged/ansible_collections/containers/podman/plugins/modules')
14 files changed, 4468 insertions, 0 deletions
diff --git a/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/__init__.py b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/__init__.py diff --git a/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_container.py b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_container.py new file mode 100644 index 00000000..99b14a39 --- /dev/null +++ b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_container.py @@ -0,0 +1,916 @@ +#!/usr/bin/python +# Copyright (c) 2020 Red Hat +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +# flake8: noqa: E501 + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + + +DOCUMENTATION = r""" +module: podman_container +author: + - "Sagi Shnaidman (@sshnaidm)" +version_added: '1.0.0' +short_description: Manage podman containers +notes: [] +description: + - Start, stop, restart and manage Podman containers +requirements: + - podman +options: + name: + description: + - Name of the container + required: True + type: str + executable: + description: + - Path to C(podman) executable if it is not in the C($PATH) on the + machine running C(podman) + default: 'podman' + type: str + state: + description: + - I(absent) - A container matching the specified name will be stopped and + removed. + - I(present) - Asserts the existence of a container matching the name and + any provided configuration parameters. If no container matches the + name, a container will be created. If a container matches the name but + the provided configuration does not match, the container will be + updated, if it can be. If it cannot be updated, it will be removed and + re-created with the requested config. Image version will be taken into + account when comparing configuration. Use the recreate option to force + the re-creation of the matching container. + - I(started) - Asserts there is a running container matching the name and + any provided configuration. If no container matches the name, a + container will be created and started. Use recreate to always re-create + a matching container, even if it is running. Use force_restart to force + a matching container to be stopped and restarted. + - I(stopped) - Asserts that the container is first I(present), and then + if the container is running moves it to a stopped state. + type: str + default: started + choices: + - absent + - present + - stopped + - started + image: + description: + - Repository path (or image name) and tag used to create the container. + If an image is not found, the image will be pulled from the registry. + If no tag is included, C(latest) will be used. + - Can also be an image ID. If this is the case, the image is assumed to + be available locally. + type: str + annotation: + description: + - Add an annotation to the container. The format is key value, multiple + times. + type: dict + authfile: + description: + - Path of the authentication file. Default is + ``${XDG_RUNTIME_DIR}/containers/auth.json`` + (Not available for remote commands) You can also override the default + path of the authentication file by setting the ``REGISTRY_AUTH_FILE`` + environment variable. ``export REGISTRY_AUTH_FILE=path`` + type: path + blkio_weight: + description: + - Block IO weight (relative weight) accepts a weight value between 10 and + 1000 + type: int + blkio_weight_device: + description: + - Block IO weight (relative device weight, format DEVICE_NAME[:]WEIGHT). + type: dict + cap_add: + description: + - List of capabilities to add to the container. + type: list + elements: str + aliases: + - capabilities + cap_drop: + description: + - List of capabilities to drop from the container. + type: list + elements: str + cgroup_parent: + description: + - Path to cgroups under which the cgroup for the container will be + created. + If the path is not absolute, the path is considered to be relative to + the cgroups path of the init process. Cgroups will be created if they + do not already exist. + type: path + cgroupns: + description: + - Path to cgroups under which the cgroup for the container will be + created. + type: str + cgroups: + description: + - Determines whether the container will create CGroups. + Valid values are enabled and disabled, which the default being enabled. + The disabled option will force the container to not create CGroups, + and thus conflicts with CGroup options cgroupns and cgroup-parent. + type: str + choices: + - default + - disabled + cidfile: + description: + - Write the container ID to the file + type: path + cmd_args: + description: + - Any additional command options you want to pass to podman command, + cmd_args - ['--other-param', 'value'] + Be aware module doesn't support idempotency if this is set. + type: list + elements: str + conmon_pidfile: + description: + - Write the pid of the conmon process to a file. + conmon runs in a separate process than Podman, + so this is necessary when using systemd to restart Podman containers. + type: path + command: + description: + - Override command of container. Can be a string or a list. + type: raw + cpu_period: + description: + - Limit the CPU real-time period in microseconds + type: int + cpu_rt_period: + description: + - Limit the CPU real-time period in microseconds. + Limit the container's Real Time CPU usage. This flag tell the kernel to + restrict the container's Real Time CPU usage to the period you specify. + type: int + cpu_rt_runtime: + description: + - Limit the CPU real-time runtime in microseconds. + This flag tells the kernel to limit the amount of time in a given CPU + period Real Time tasks may consume. + type: int + cpu_shares: + description: + - CPU shares (relative weight) + type: int + cpus: + description: + - Number of CPUs. The default is 0.0 which means no limit. + type: str + cpuset_cpus: + description: + - CPUs in which to allow execution (0-3, 0,1) + type: str + cpuset_mems: + description: + - Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only + effective on NUMA systems. + type: str + detach: + description: + - Run container in detach mode + type: bool + default: True + debug: + description: + - Return additional information which can be helpful for investigations. + type: bool + default: False + detach_keys: + description: + - Override the key sequence for detaching a container. Format is a single + character or ctrl-value + type: str + device: + description: + - Add a host device to the container. + The format is <device-on-host>[:<device-on-container>][:<permissions>] + (e.g. device /dev/sdc:/dev/xvdc:rwm) + type: list + elements: str + device_read_bps: + description: + - Limit read rate (bytes per second) from a device + (e.g. device-read-bps /dev/sda:1mb) + type: list + device_read_iops: + description: + - Limit read rate (IO per second) from a device + (e.g. device-read-iops /dev/sda:1000) + type: list + device_write_bps: + description: + - Limit write rate (bytes per second) to a device + (e.g. device-write-bps /dev/sda:1mb) + type: list + device_write_iops: + description: + - Limit write rate (IO per second) to a device + (e.g. device-write-iops /dev/sda:1000) + type: list + dns: + description: + - Set custom DNS servers + type: list + elements: str + aliases: + - dns_servers + dns_option: + description: + - Set custom DNS options + type: str + aliases: + - dns_opts + dns_search: + description: + - Set custom DNS search domains (Use dns_search with '' if you don't wish + to set the search domain) + type: str + aliases: + - dns_search_domains + entrypoint: + description: + - Overwrite the default ENTRYPOINT of the image + type: str + env: + description: + - Set environment variables. + This option allows you to specify arbitrary environment variables that + are available for the process that will be launched inside of the + container. + type: dict + env_file: + description: + - Read in a line delimited file of environment variables + type: path + env_host: + description: + - Use all current host environment variables in container. + Defaults to false. + type: bool + etc_hosts: + description: + - Dict of host-to-IP mappings, where each host name is a key in the + dictionary. Each host name will be added to the container's + ``/etc/hosts`` file. + type: dict + aliases: + - add_hosts + expose: + description: + - Expose a port, or a range of ports (e.g. expose "3300-3310") to set up + port redirection on the host system. + type: list + elements: str + aliases: + - exposed + - exposed_ports + force_restart: + description: + - Force restart of container. + type: bool + default: False + aliases: + - restart + gidmap: + description: + - Run the container in a new user namespace using the supplied mapping. + type: list + elements: str + group_add: + description: + - Add additional groups to run as + type: list + aliases: + - groups + healthcheck: + description: + - Set or alter a healthcheck command for a container. + type: str + healthcheck_interval: + description: + - Set an interval for the healthchecks + (a value of disable results in no automatic timer setup) + (default "30s") + type: str + healthcheck_retries: + description: + - The number of retries allowed before a healthcheck is considered to be + unhealthy. The default value is 3. + type: int + healthcheck_start_period: + description: + - The initialization time needed for a container to bootstrap. + The value can be expressed in time format like 2m3s. The default value + is 0s + type: str + healthcheck_timeout: + description: + - The maximum time allowed to complete the healthcheck before an interval + is considered failed. Like start-period, the value can be expressed in + a time format such as 1m22s. The default value is 30s + type: str + hostname: + description: + - Container host name. Sets the container host name that is available + inside the container. + type: str + http_proxy: + description: + - By default proxy environment variables are passed into the container if + set for the podman process. This can be disabled by setting the + http_proxy option to false. The environment variables passed in + include http_proxy, https_proxy, ftp_proxy, no_proxy, and also the + upper case versions of those. + Defaults to true + type: bool + image_volume: + description: + - Tells podman how to handle the builtin image volumes. + The options are bind, tmpfs, or ignore (default bind) + type: str + choices: + - 'bind' + - 'tmpfs' + - 'ignore' + image_strict: + description: + - Whether to compare images in idempotency by taking into account a full + name with registry and namespaces. + type: bool + default: False + init: + description: + - Run an init inside the container that forwards signals and reaps + processes. The default is false. + type: bool + init_path: + description: + - Path to the container-init binary. + type: str + interactive: + description: + - Keep STDIN open even if not attached. The default is false. + When set to true, keep stdin open even if not attached. + The default is false. + type: bool + ip: + description: + - Specify a static IP address for the container, for example + '10.88.64.128'. + Can only be used if no additional CNI networks to join were specified + via 'network:', and if the container is not joining another container's + network namespace via 'network container:<name|id>'. + The address must be within the default CNI network's pool + (default 10.88.0.0/16). + type: str + ipc: + description: + - Default is to create a private IPC namespace (POSIX SysV IPC) for the + container + type: str + aliases: + - ipc_mode + kernel_memory: + description: + - Kernel memory limit + (format <number>[<unit>], where unit = b, k, m or g) + Note - idempotency is supported for integers only. + type: str + label: + description: + - Add metadata to a container, pass dictionary of label names and values + aliases: + - labels + type: dict + label_file: + description: + - Read in a line delimited file of labels + type: str + log_driver: + description: + - Logging driver. Used to set the log driver for the container. + For example log_driver "k8s-file". + type: str + choices: + - k8s-file + - journald + - json-file + log_level: + description: + - Logging level for Podman. Log messages above specified level + ("debug"|"info"|"warn"|"error"|"fatal"|"panic") (default "error") + type: str + choices: + - debug + - info + - warn + - error + - fatal + - panic + log_opt: + description: + - Logging driver specific options. Used to set the path to the container + log file. + type: dict + aliases: + - log_options + suboptions: + path: + description: + - Specify a path to the log file (e.g. /var/log/container/mycontainer.json). + type: str + required: false + max_size: + description: + - Specify a max size of the log file (e.g 10mb). + type: str + required: false + tag: + description: + - Specify a custom log tag for the container. + type: str + required: false + + mac_address: + description: + - Specify a MAC address for the container, for example + '92:d0:c6:0a:29:33'. + Don't forget that it must be unique within one Ethernet network. + type: str + memory: + description: + - Memory limit (format 10k, where unit = b, k, m or g) + Note - idempotency is supported for integers only. + type: str + memory_reservation: + description: + - Memory soft limit (format 100m, where unit = b, k, m or g) + Note - idempotency is supported for integers only. + type: str + memory_swap: + description: + - A limit value equal to memory plus swap. Must be used with the -m + (--memory) flag. + The swap LIMIT should always be larger than -m (--memory) value. + By default, the swap LIMIT will be set to double the value of --memory + Note - idempotency is supported for integers only. + type: str + memory_swappiness: + description: + - Tune a container's memory swappiness behavior. Accepts an integer + between 0 and 100. + type: int + mount: + description: + - Attach a filesystem mount to the container. bind or tmpfs + For example mount + "type=bind,source=/path/on/host,destination=/path/in/container" + type: str + network: + description: + - Set the Network mode for the container + * bridge create a network stack on the default bridge + * none no networking + * container:<name|id> reuse another container's network stack + * host use the podman host network stack. + * <network-name>|<network-id> connect to a user-defined network + * ns:<path> path to a network namespace to join + * slirp4netns use slirp4netns to create a user network stack. + This is the default for rootless containers + type: list + elements: str + aliases: + - net + - network_mode + no_hosts: + description: + - Do not create /etc/hosts for the container + Default is false. + type: bool + oom_kill_disable: + description: + - Whether to disable OOM Killer for the container or not. + Default is false. + type: bool + oom_score_adj: + description: + - Tune the host's OOM preferences for containers (accepts -1000 to 1000) + type: int + pid: + description: + - Set the PID mode for the container + type: str + aliases: + - pid_mode + pids_limit: + description: + - Tune the container's PIDs limit. Set -1 to have unlimited PIDs for the + container. + type: str + pod: + description: + - Run container in an existing pod. + If you want podman to make the pod for you, preference the pod name + with "new:" + type: str + privileged: + description: + - Give extended privileges to this container. The default is false. + type: bool + publish: + description: + - Publish a container's port, or range of ports, to the host. + Format - ip:hostPort:containerPort | ip::containerPort | + hostPort:containerPort | containerPort + In case of only containerPort is set, the hostPort will chosen + randomly by Podman. + type: list + elements: str + aliases: + - ports + - published + - published_ports + publish_all: + description: + - Publish all exposed ports to random ports on the host interfaces. The + default is false. + type: bool + read_only: + description: + - Mount the container's root filesystem as read only. Default is false + type: bool + read_only_tmpfs: + description: + - If container is running in --read-only mode, then mount a read-write + tmpfs on /run, /tmp, and /var/tmp. The default is true + type: bool + recreate: + description: + - Use with present and started states to force the re-creation of an + existing container. + type: bool + default: False + restart_policy: + description: + - Restart policy to follow when containers exit. + Restart policy will not take effect if a container is stopped via the + podman kill or podman stop commands. Valid values are + * no - Do not restart containers on exit + * on-failure[:max_retries] - Restart containers when they exit with a + non-0 exit code, retrying indefinitely + or until the optional max_retries count is hit + * always - Restart containers when they exit, regardless of status, + retrying indefinitely + type: str + rm: + description: + - Automatically remove the container when it exits. The default is false. + type: bool + aliases: + - remove + - auto_remove + rootfs: + description: + - If true, the first argument refers to an exploded container on the file + system. The default is false. + type: bool + security_opt: + description: + - Security Options. For example security_opt "seccomp=unconfined" + type: list + elements: str + shm_size: + description: + - Size of /dev/shm. The format is <number><unit>. number must be greater + than 0. + Unit is optional and can be b (bytes), k (kilobytes), m(megabytes), or + g (gigabytes). + If you omit the unit, the system uses bytes. If you omit the size + entirely, the system uses 64m + type: str + sig_proxy: + description: + - Proxy signals sent to the podman run command to the container process. + SIGCHLD, SIGSTOP, and SIGKILL are not proxied. The default is true. + type: bool + stop_signal: + description: + - Signal to stop a container. Default is SIGTERM. + type: int + stop_timeout: + description: + - Timeout (in seconds) to stop a container. Default is 10. + type: int + subgidname: + description: + - Run the container in a new user namespace using the map with 'name' in + the /etc/subgid file. + type: str + subuidname: + description: + - Run the container in a new user namespace using the map with 'name' in + the /etc/subuid file. + type: str + sysctl: + description: + - Configure namespaced kernel parameters at runtime + type: dict + systemd: + description: + - Run container in systemd mode. The default is true. + type: bool + tmpfs: + description: + - Create a tmpfs mount. For example tmpfs + "/tmp" "rw,size=787448k,mode=1777" + type: dict + tty: + description: + - Allocate a pseudo-TTY. The default is false. + type: bool + uidmap: + description: + - Run the container in a new user namespace using the supplied mapping. + type: list + elements: str + ulimit: + description: + - Ulimit options + type: list + aliases: + - ulimits + user: + description: + - Sets the username or UID used and optionally the groupname or GID for + the specified command. + type: str + userns: + description: + - Set the user namespace mode for the container. + It defaults to the PODMAN_USERNS environment variable. + An empty value means user namespaces are disabled. + type: str + aliases: + - userns_mode + uts: + description: + - Set the UTS mode for the container + type: str + volume: + description: + - Create a bind mount. If you specify, volume /HOST-DIR:/CONTAINER-DIR, + podman bind mounts /HOST-DIR in the host to /CONTAINER-DIR in the + podman container. + type: list + elements: str + aliases: + - volumes + volumes_from: + description: + - Mount volumes from the specified container(s). + type: list + elements: str + workdir: + description: + - Working directory inside the container. + The default working directory for running binaries within a container + is the root directory (/). + type: str + aliases: + - working_dir +""" + +EXAMPLES = r""" +- name: Run container + containers.podman.podman_container: + name: container + image: quay.io/bitnami/wildfly + state: started + +- name: Create a data container + containers.podman.podman_container: + name: mydata + image: busybox + volume: + - /tmp/data + +- name: Re-create a redis container + containers.podman.podman_container: + name: myredis + image: redis + command: redis-server --appendonly yes + state: present + recreate: yes + expose: + - 6379 + volumes_from: + - mydata + +- name: Restart a container + containers.podman.podman_container: + name: myapplication + image: redis + state: started + restart: yes + etc_hosts: + other: "127.0.0.1" + restart_policy: "no" + device: "/dev/sda:/dev/xvda:rwm" + ports: + - "8080:9000" + - "127.0.0.1:8081:9001/udp" + env: + SECRET_KEY: "ssssh" + BOOLEAN_KEY: "yes" + +- name: Container present + containers.podman.podman_container: + name: mycontainer + state: present + image: ubuntu:14.04 + command: "sleep 1d" + +- name: Stop a container + containers.podman.podman_container: + name: mycontainer + state: stopped + +- name: Start 4 load-balanced containers + containers.podman.podman_container: + name: "container{{ item }}" + recreate: yes + image: someuser/anotherappimage + command: sleep 1d + with_sequence: count=4 + +- name: remove container + containers.podman.podman_container: + name: ohno + state: absent + +- name: Writing output + containers.podman.podman_container: + name: myservice + image: busybox + log_options: path=/var/log/container/mycontainer.json + log_driver: k8s-file +""" + +RETURN = r""" +container: + description: + - Facts representing the current state of the container. Matches the + podman inspection output. + - Note that facts are part of the registered vars since Ansible 2.8. For + compatibility reasons, the facts + are also accessible directly as C(podman_container). Note that the + returned fact will be removed in Ansible 2.12. + - Empty if C(state) is I(absent). + returned: always + type: dict + sample: '{ + "AppArmorProfile": "", + "Args": [ + "sh" + ], + "BoundingCaps": [ + "CAP_CHOWN", + ... + ], + "Config": { + "Annotations": { + "io.kubernetes.cri-o.ContainerType": "sandbox", + "io.kubernetes.cri-o.TTY": "false" + }, + "AttachStderr": false, + "AttachStdin": false, + "AttachStdout": false, + "Cmd": [ + "sh" + ], + "Domainname": "", + "Entrypoint": "", + "Env": [ + "PATH=/usr/sbin:/usr/bin:/sbin:/bin", + "TERM=xterm", + "HOSTNAME=", + "container=podman" + ], + "Hostname": "", + "Image": "docker.io/library/busybox:latest", + "Labels": null, + "OpenStdin": false, + "StdinOnce": false, + "StopSignal": 15, + "Tty": false, + "User": { + "gid": 0, + "uid": 0 + }, + "Volumes": null, + "WorkingDir": "/" + }, + "ConmonPidFile": "...", + "Created": "2019-06-17T19:13:09.873858307+03:00", + "Dependencies": [], + "Driver": "overlay", + "EffectiveCaps": [ + "CAP_CHOWN", + ... + ], + "ExecIDs": [], + "ExitCommand": [ + "/usr/bin/podman", + "--root", + ... + ], + "GraphDriver": { + ... + }, + "HostConfig": { + ... + }, + "HostnamePath": "...", + "HostsPath": "...", + "ID": "...", + "Image": "...", + "ImageName": "docker.io/library/busybox:latest", + "IsInfra": false, + "LogPath": "/tmp/container/mycontainer.json", + "MountLabel": "system_u:object_r:container_file_t:s0:c282,c782", + "Mounts": [ + ... + ], + "Name": "myservice", + "Namespace": "", + "NetworkSettings": { + "Bridge": "", + ... + }, + "Path": "sh", + "ProcessLabel": "system_u:system_r:container_t:s0:c282,c782", + "ResolvConfPath": "...", + "RestartCount": 0, + "Rootfs": "", + "State": { + "Dead": false, + "Error": "", + "ExitCode": 0, + "FinishedAt": "2019-06-17T19:13:10.157518963+03:00", + "Healthcheck": { + "FailingStreak": 0, + "Log": null, + "Status": "" + }, + "OOMKilled": false, + "OciVersion": "1.0.1-dev", + "Paused": false, + "Pid": 4083, + "Restarting": false, + "Running": false, + "StartedAt": "2019-06-17T19:13:10.152479729+03:00", + "Status": "exited" + }, + "StaticDir": "..." + ... + }' +""" + +from ansible.module_utils.basic import AnsibleModule # noqa: F402 +from ..module_utils.podman.podman_container_lib import PodmanManager # noqa: F402 +from ..module_utils.podman.podman_container_lib import ARGUMENTS_SPEC_CONTAINER # noqa: F402 + + +def main(): + module = AnsibleModule( + argument_spec=ARGUMENTS_SPEC_CONTAINER, + mutually_exclusive=( + ['no_hosts', 'etc_hosts'], + ), + supports_check_mode=True, + ) + + # work on input vars + if module.params['state'] in ['started', 'present'] and \ + not module.params['image']: + module.fail_json(msg="State '%s' required image to be configured!" % + module.params['state']) + + results = PodmanManager(module, module.params).execute() + module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_container_info.py b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_container_info.py new file mode 100644 index 00000000..d3ec55c8 --- /dev/null +++ b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_container_info.py @@ -0,0 +1,405 @@ +#!/usr/bin/python +# Copyright (c) 2020 Red Hat +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = r''' +module: podman_container_info +author: + - Sagi Shnaidman (@sshnaidm) + - Emilien Macchi (@EmilienM) +short_description: Gather facts about containers using podman +notes: + - Podman may require elevated privileges in order to run properly. +description: + - Gather facts about containers using C(podman) +requirements: + - "Podman installed on host" +options: + name: + description: + - List of container names to gather facts about. If no name is given + return facts about all containers. + type: list + elements: str + executable: + description: + - Path to C(podman) executable if it is not in the C($PATH) on the + machine running C(podman) + default: 'podman' + type: str +''' + +EXAMPLES = r""" +- name: Gather facts for all containers + containers.podman.podman_container_info: + +- name: Gather facts on a specific container + containers.podman.podman_container_info: + name: web1 + +- name: Gather facts on several containers + containers.podman.podman_container_info: + name: + - redis + - web1 +""" + +RETURN = r""" +containers: + description: Facts from all or specificed containers + returned: always + type: list + elements: dict + sample: [ + { + "Id": "c5c39f9b80a6ea2ad665aa9946435934e478a0c5322da835f3883872f", + "Created": "2019-10-01T12:51:00.233106443Z", + "Path": "dumb-init", + "Args": [ + "--single-child", + "--", + "kolla_start" + ], + "State": { + "OciVersion": "1.0.1-dev", + "Status": "configured", + "Running": false, + "Paused": false, + "Restarting": false, + "OOMKilled": false, + "Dead": false, + "Pid": 0, + "ExitCode": 0, + "Error": "", + "StartedAt": "0001-01-01T00:00:00Z", + "FinishedAt": "0001-01-01T00:00:00Z", + "Healthcheck": { + "Status": "", + "FailingStreak": 0, + "Log": null + } + }, + "Image": "0e267acda67d0ebd643e900d820a91b961d859743039e620191ca1", + "ImageName": "docker.io/tripleomaster/centos-haproxy:latest", + "Rootfs": "", + "Pod": "", + "ResolvConfPath": "", + "HostnamePath": "", + "HostsPath": "", + "OCIRuntime": "runc", + "Name": "haproxy", + "RestartCount": 0, + "Driver": "overlay", + "MountLabel": "system_u:object_r:svirt_sandbox_file_t:s0:c78,c866", + "ProcessLabel": "system_u:system_r:svirt_lxc_net_t:s0:c785,c866", + "AppArmorProfile": "", + "EffectiveCaps": [ + "CAP_CHOWN", + "CAP_DAC_OVERRIDE", + "CAP_FSETID", + "CAP_FOWNER", + "CAP_MKNOD", + "CAP_NET_RAW", + "CAP_SETGID", + "CAP_SETUID", + "CAP_SETFCAP", + "CAP_SETPCAP", + "CAP_NET_BIND_SERVICE", + "CAP_SYS_CHROOT", + "CAP_KILL", + "CAP_AUDIT_WRITE" + ], + "BoundingCaps": [ + "CAP_CHOWN", + "CAP_DAC_OVERRIDE", + "CAP_FSETID", + "CAP_FOWNER", + "CAP_MKNOD", + "CAP_NET_RAW", + "CAP_SETGID", + "CAP_SETUID", + "CAP_SETFCAP", + "CAP_SETPCAP", + "CAP_NET_BIND_SERVICE", + "CAP_SYS_CHROOT", + "CAP_KILL", + "CAP_AUDIT_WRITE" + ], + "ExecIDs": [], + "GraphDriver": { + "Name": "overlay" + }, + "Mounts": [], + "Dependencies": [], + "NetworkSettings": { + "Bridge": "", + "SandboxID": "", + "HairpinMode": false, + "LinkLocalIPv6Address": "", + "LinkLocalIPv6PrefixLen": 0, + "Ports": [], + "SandboxKey": "", + "SecondaryIPAddresses": null, + "SecondaryIPv6Addresses": null, + "EndpointID": "", + "Gateway": "", + "GlobalIPv6Address": "", + "GlobalIPv6PrefixLen": 0, + "IPAddress": "", + "IPPrefixLen": 0, + "IPv6Gateway": "", + "MacAddress": "" + }, + "ExitCommand": [ + "/usr/bin/podman", + "--root", + "/var/lib/containers/storage", + "--runroot", + "/var/run/containers/storage", + "--log-level", + "error", + "--cgroup-manager", + "systemd", + "--tmpdir", + "/var/run/libpod", + "--runtime", + "runc", + "--storage-driver", + "overlay", + "--events-backend", + "journald", + "container", + "cleanup", + "c9e813703f9b80a6ea2ad665aa9946435934e478a0c5322da835f3883872f" + ], + "Namespace": "", + "IsInfra": false, + "Config": { + "Hostname": "c5c39e813703", + "Domainname": "", + "User": "", + "AttachStdin": false, + "AttachStdout": false, + "AttachStderr": false, + "Tty": false, + "OpenStdin": false, + "StdinOnce": false, + "Env": [ + "PATH=/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "TERM=xterm", + "HOSTNAME=", + "container=oci", + "KOLLA_INSTALL_METATYPE=rdo", + "KOLLA_BASE_DISTRO=centos", + "KOLLA_INSTALL_TYPE=binary", + "KOLLA_DISTRO_PYTHON_VERSION=2.7", + "KOLLA_BASE_ARCH=x86_64" + ], + "Cmd": [ + "kolla_start" + ], + "Image": "docker.io/tripleomaster/centos-haproxy:latest", + "Volumes": null, + "WorkingDir": "/", + "Entrypoint": "dumb-init --single-child --", + "OnBuild": null, + "Labels": { + "build-date": "20190919", + "kolla_version": "8.1.0", + "name": "haproxy", + "org.label-schema.build-date": "20190801", + "org.label-schema.license": "GPLv2", + "org.label-schema.name": "CentOS Base Image", + "org.label-schema.schema-version": "1.0", + "org.label-schema.vendor": "CentOS" + }, + "Annotations": { + "io.kubernetes.cri-o.ContainerType": "sandbox", + "io.kubernetes.cri-o.TTY": "false", + "io.podman.annotations.autoremove": "FALSE", + "io.podman.annotations.init": "FALSE", + "io.podman.annotations.privileged": "FALSE", + "io.podman.annotations.publish-all": "FALSE" + }, + "StopSignal": 15 + }, + "HostConfig": { + "Binds": [], + "ContainerIDFile": "", + "LogConfig": { + "Type": "k8s-file", + "Config": null + }, + "NetworkMode": "default", + "PortBindings": {}, + "RestartPolicy": { + "Name": "", + "MaximumRetryCount": 0 + }, + "AutoRemove": false, + "VolumeDriver": "", + "VolumesFrom": null, + "CapAdd": [], + "CapDrop": [], + "Dns": [], + "DnsOptions": [], + "DnsSearch": [], + "ExtraHosts": [], + "GroupAdd": [], + "IpcMode": "", + "Cgroup": "", + "Links": null, + "OomScoreAdj": 0, + "PidMode": "", + "Privileged": false, + "PublishAllPorts": false, + "ReadonlyRootfs": false, + "SecurityOpt": [], + "Tmpfs": {}, + "UTSMode": "", + "UsernsMode": "", + "ShmSize": 65536000, + "Runtime": "oci", + "ConsoleSize": [ + 0, + 0 + ], + "Isolation": "", + "CpuShares": 0, + "Memory": 0, + "NanoCpus": 0, + "CgroupParent": "", + "BlkioWeight": 0, + "BlkioWeightDevice": null, + "BlkioDeviceReadBps": null, + "BlkioDeviceWriteBps": null, + "BlkioDeviceReadIOps": null, + "BlkioDeviceWriteIOps": null, + "CpuPeriod": 0, + "CpuQuota": 0, + "CpuRealtimePeriod": 0, + "CpuRealtimeRuntime": 0, + "CpusetCpus": "", + "CpusetMems": "", + "Devices": [], + "DiskQuota": 0, + "KernelMemory": 0, + "MemoryReservation": 0, + "MemorySwap": 0, + "MemorySwappiness": -1, + "OomKillDisable": false, + "PidsLimit": 0, + "Ulimits": [ + { + "Name": "RLIMIT_NOFILE", + "Soft": 1048576, + "Hard": 1048576 + }, + { + "Name": "RLIMIT_NPROC", + "Soft": 1048576, + "Hard": 1048576 + } + ], + "CpuCount": 0, + "CpuPercent": 0, + "IOMaximumIOps": 0, + "IOMaximumBandwidth": 0 + } + } + ] +""" + +import json +from ansible.module_utils.basic import AnsibleModule + + +def get_containers_facts(module, executable, name): + """Collect containers facts for all containers or for specified in 'name'. + + Arguments: + module {AnsibleModule} -- instance of AnsibleModule + executable {string} -- binary to execute when inspecting containers + name {list} -- list of names or None in case of all containers + + Returns: + list of containers info, stdout, stderr + """ + if not name: + all_names = [executable, 'container', 'ls', '-q', '-a'] + rc, out, err = module.run_command(all_names) + if rc != 0: + module.fail_json(msg="Unable to get list of containers: %s" % err) + name = out.split() + if not name: + return [], out, err + command = [executable, 'container', 'inspect'] + command.extend(name) + rc, out, err = module.run_command(command) + if rc == 0: + json_out = json.loads(out) if out else None + if json_out is None: + return [], out, err + return json_out, out, err + if rc != 0 and 'no such ' in err: + if len(name) < 2: + return [], out, err + return cycle_over(module, executable, name) + module.fail_json(msg="Unable to gather info for %s: %s" % (",".join(name), err)) + + +def cycle_over(module, executable, name): + """Inspect each container in a cycle in case some of them don't exist. + + Arguments: + module {AnsibleModule} -- instance of AnsibleModule + executable {string} -- binary to execute when inspecting containers + name {list} -- list of containers names to inspect + + Returns: + list of containers info, stdout as empty, stderr + """ + inspection = [] + stderrs = [] + for container in name: + command = [executable, 'container', 'inspect', container] + rc, out, err = module.run_command(command) + if rc != 0 and 'no such ' not in err: + module.fail_json(msg="Unable to gather info for %s: %s" % (container, err)) + if rc == 0 and out: + json_out = json.loads(out) + if json_out: + inspection += json_out + stderrs.append(err) + return inspection, "", "\n".join(stderrs) + + +def main(): + module = AnsibleModule( + argument_spec={ + 'executable': {'type': 'str', 'default': 'podman'}, + 'name': {'type': 'list', 'elements': 'str'}, + }, + supports_check_mode=True, + ) + + name = module.params['name'] + executable = module.get_bin_path(module.params['executable'], required=True) + # pylint: disable=unused-variable + inspect_results, out, err = get_containers_facts(module, executable, name) + + results = { + "changed": False, + "containers": inspect_results, + "stderr": err + } + + module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_containers.py b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_containers.py new file mode 100644 index 00000000..75ebb057 --- /dev/null +++ b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_containers.py @@ -0,0 +1,162 @@ +#!/usr/bin/python +# Copyright (c) 2020 Red Hat +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: podman_containers +author: + - "Sagi Shnaidman (@sshnaidm)" +version_added: '1.4.0' +short_description: Manage podman containers in a batch +description: + - Manage groups of podman containers +requirements: + - "podman" +options: + containers: + description: + - List of dictionaries with data for running containers for podman_container module. + required: True + type: list + elements: dict + debug: + description: + - Return additional information which can be helpful for investigations. + type: bool + default: False +''' + +EXAMPLES = ''' +- name: Run three containers at once + podman_containers: + containers: + - name: alpine + image: alpine + command: sleep 1d + - name: web + image: nginx + - name: test + image: python:3-alpine + command: python -V +''' + +from copy import deepcopy # noqa: F402 + +from ansible.module_utils.basic import AnsibleModule # noqa: F402 +from ..module_utils.podman.podman_container_lib import PodmanManager # noqa: F402 +from ..module_utils.podman.podman_container_lib import ARGUMENTS_SPEC_CONTAINER # noqa: F402 + + +def init_options(): + default = {} + opts = ARGUMENTS_SPEC_CONTAINER + for k, v in opts.items(): + if 'default' in v: + default[k] = v['default'] + else: + default[k] = None + return default + + +def update_options(opts_dict, container): + aliases = {} + for k, v in ARGUMENTS_SPEC_CONTAINER.items(): + if 'aliases' in v: + for alias in v['aliases']: + aliases[alias] = k + for k in list(container): + if k in aliases: + key = aliases[k] + opts_dict[key] = container[k] + container.pop(k) + opts_dict.update(container) + return opts_dict + + +def combine(results): + changed = any([i.get('changed', False) for i in results]) + failed = any([i.get('failed', False) for i in results]) + actions = [] + podman_actions = [] + containers = [] + podman_version = '' + diffs = {} + stderr = '' + stdout = '' + for i in results: + if 'actions' in i and i['actions']: + actions += i['actions'] + if 'podman_actions' in i and i['podman_actions']: + podman_actions += i['podman_actions'] + if 'container' in i and i['container']: + containers.append(i['container']) + if 'podman_version' in i: + podman_version = i['podman_version'] + if 'diff' in i: + diffs[i['container']['Name']] = i['diff'] + if 'stderr' in i: + stderr += i['stderr'] + if 'stdout' in i: + stdout += i['stdout'] + + total = { + 'changed': changed, + 'failed': failed, + 'actions': actions, + 'podman_actions': podman_actions, + 'containers': containers, + 'stdout': stdout, + 'stderr': stderr, + } + if podman_version: + total['podman_version'] = podman_version + if diffs: + before = after = '' + for k, v in diffs.items(): + before += "".join([str(k), ": ", str(v['before']), "\n"]) + after += "".join([str(k), ": ", str(v['after']), "\n"]) + total['diff'] = { + 'before': before, + 'after': after + } + return total + + +def check_input_strict(container): + if container['state'] in ['started', 'present'] and not container['image']: + return "State '%s' required image to be configured!" % container['state'] + + +def main(): + module = AnsibleModule( + argument_spec=dict( + containers=dict(type='list', elements='dict', required=True), + debug=dict(type='bool', default=False), + ), + supports_check_mode=True, + ) + # work on input vars + + results = [] + default_options_templ = init_options() + for container in module.params['containers']: + options_dict = deepcopy(default_options_templ) + options_dict = update_options(options_dict, container) + options_dict['debug'] = module.params['debug'] or options_dict['debug'] + test_input = check_input_strict(options_dict) + if test_input: + module.fail_json( + msg="Failed to run container %s because: %s" % (options_dict['name'], test_input)) + res = PodmanManager(module, options_dict).execute() + results.append(res) + total_results = combine(results) + module.exit_json(**total_results) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_image.py b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_image.py new file mode 100644 index 00000000..186f0498 --- /dev/null +++ b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_image.py @@ -0,0 +1,774 @@ +#!/usr/bin/python +# Copyright (c) 2018 Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = r''' + module: podman_image + author: + - Sam Doran (@samdoran) + short_description: Pull images for use by podman + notes: [] + description: + - Build, pull, or push images using Podman. + options: + name: + description: + - Name of the image to pull, push, or delete. It may contain a tag using the format C(image:tag). + required: True + type: str + executable: + description: + - Path to C(podman) executable if it is not in the C($PATH) on the machine running C(podman). + default: 'podman' + type: str + ca_cert_dir: + description: + - Path to directory containing TLS certificates and keys to use. + type: 'path' + tag: + description: + - Tag of the image to pull, push, or delete. + default: "latest" + type: str + pull: + description: Whether or not to pull the image. + default: True + type: bool + push: + description: Whether or not to push an image. + default: False + type: bool + path: + description: Path to directory containing the build file. + type: str + force: + description: + - Whether or not to force push or pull an image. + - When building, force the build even if the image already exists. + type: bool + default: False + state: + description: + - Whether an image should be present, absent, or built. + default: "present" + type: str + choices: + - present + - absent + - build + validate_certs: + description: + - Require HTTPS and validate certificates when pulling or pushing. Also used during build if a pull or push is necessary. + default: True + type: bool + aliases: + - tlsverify + - tls_verify + password: + description: + - Password to use when authenticating to remote registries. + type: str + username: + description: + - username to use when authenticating to remote registries. + type: str + auth_file: + description: + - Path to file containing authorization credentials to the remote registry. + aliases: + - authfile + type: path + build: + description: Arguments that control image build. + type: dict + aliases: + - build_args + - buildargs + suboptions: + volume: + description: + - Specify multiple volume / mount options to mount one or more mounts to a container. + type: list + elements: str + annotation: + description: + - Dictionary of key=value pairs to add to the image. Only works with OCI images. Ignored for Docker containers. + type: dict + force_rm: + description: + - Always remove intermediate containers after a build, even if the build is unsuccessful. + type: bool + default: False + format: + description: + - Format of the built image. + type: str + choices: + - docker + - oci + default: "oci" + cache: + description: + - Whether or not to use cached layers when building an image + type: bool + default: True + rm: + description: Remove intermediate containers after a successful build + type: bool + default: True + extra_args: + description: + - Extra args to pass to build, if executed. Does not idempotently check for new build args. + type: str + push_args: + description: Arguments that control pushing images. + type: dict + suboptions: + compress: + description: + - Compress tarball image layers when pushing to a directory using the 'dir' transport. + type: bool + format: + description: + - Manifest type to use when pushing an image using the 'dir' transport (default is manifest type of source). + type: str + choices: + - oci + - v2s1 + - v2s2 + remove_signatures: + description: Discard any pre-existing signatures in the image + type: bool + sign_by: + description: + - Path to a key file to use to sign the image. + type: str + dest: + description: Path or URL where image will be pushed. + type: str + aliases: + - destination + transport: + description: + - Transport to use when pushing in image. If no transport is set, will attempt to push to a remote registry. + type: str + choices: + - dir + - docker-archive + - docker-daemon + - oci-archive + - ostree +''' + +EXAMPLES = r""" +- name: Pull an image + container.podman.podman_image: + name: quay.io/bitnami/wildfly + +- name: Remove an image + container.podman.podman_image: + name: quay.io/bitnami/wildfly + state: absent + +- name: Pull a specific version of an image + container.podman.podman_image: + name: redis + tag: 4 + +- name: Build a basic OCI image + container.podman.podman_image: + name: nginx + path: /path/to/build/dir + +- name: Build a basic OCI image with advanced parameters + container.podman.podman_image: + name: nginx + path: /path/to/build/dir + build: + cache: no + force_rm: yes + format: oci + annotation: + app: nginx + function: proxy + info: Load balancer for my cool app + +- name: Build a Docker formatted image + container.podman.podman_image: + name: nginx + path: /path/to/build/dir + build: + format: docker + +- name: Build and push an image using existing credentials + container.podman.podman_image: + name: nginx + path: /path/to/build/dir + push: yes + push_args: + dest: quay.io/acme + +- name: Build and push an image using an auth file + container.podman.podman_image: + name: nginx + push: yes + auth_file: /etc/containers/auth.json + push_args: + dest: quay.io/acme + +- name: Build and push an image using username and password + container.podman.podman_image: + name: nginx + push: yes + username: bugs + password: "{{ vault_registry_password }}" + push_args: + dest: quay.io/acme + +- name: Build and push an image to multiple registries + container.podman.podman_image: + name: "{{ item }}" + path: /path/to/build/dir + push: yes + auth_file: /etc/containers/auth.json + loop: + - quay.io/acme/nginx + - docker.io/acme/nginx + +- name: Build and push an image to multiple registries with separate parameters + container.podman.podman_image: + name: "{{ item.name }}" + tag: "{{ item.tag }}" + path: /path/to/build/dir + push: yes + auth_file: /etc/containers/auth.json + push_args: + dest: "{{ item.dest }}" + loop: + - name: nginx + tag: 4 + dest: docker.io/acme + + - name: nginx + tag: 3 + dest: docker.io/acme +""" + +RETURN = r""" + image: + description: + - Image inspection results for the image that was pulled, pushed, or built. + returned: success + type: dict + sample: [ + { + "Annotations": {}, + "Architecture": "amd64", + "Author": "", + "Comment": "from Bitnami with love", + "ContainerConfig": { + "Cmd": [ + "/run.sh" + ], + "Entrypoint": [ + "/app-entrypoint.sh" + ], + "Env": [ + "PATH=/opt/bitnami/java/bin:/opt/bitnami/wildfly/bin:/opt/bitnami/nami/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "IMAGE_OS=debian-9", + "NAMI_VERSION=1.0.0-1", + "GPG_KEY_SERVERS_LIST=ha.pool.sks-keyservers.net", + "TINI_VERSION=v0.13.2", + "TINI_GPG_KEY=595E85A6B1B4779EA4DAAEC70B588DFF0527A9B7", + "GOSU_VERSION=1.10", + "GOSU_GPG_KEY=B42F6819007F00F88E364FD4036A9C25BF357DD4", + "BITNAMI_IMAGE_VERSION=16.0.0-debian-9-r27", + "BITNAMI_PKG_CHMOD=-R g+rwX", + "BITNAMI_PKG_EXTRA_DIRS=/home/wildfly", + "HOME=/", + "BITNAMI_APP_NAME=wildfly", + "NAMI_PREFIX=/.nami", + "WILDFLY_HOME=/home/wildfly", + "WILDFLY_JAVA_HOME=", + "WILDFLY_JAVA_OPTS=", + "WILDFLY_MANAGEMENT_HTTP_PORT_NUMBER=9990", + "WILDFLY_PASSWORD=bitnami", + "WILDFLY_PUBLIC_CONSOLE=true", + "WILDFLY_SERVER_AJP_PORT_NUMBER=8009", + "WILDFLY_SERVER_HTTP_PORT_NUMBER=8080", + "WILDFLY_SERVER_INTERFACE=0.0.0.0", + "WILDFLY_USERNAME=user", + "WILDFLY_WILDFLY_HOME=/home/wildfly", + "WILDFLY_WILDFLY_OPTS=-Dwildfly.as.deployment.ondemand=false" + ], + "ExposedPorts": { + "8080/tcp": {}, + "9990/tcp": {} + }, + "Labels": { + "maintainer": "Bitnami <containers@bitnami.com>" + }, + "User": "1001" + }, + "Created": "2019-04-10T05:48:03.553887623Z", + "Digest": "sha256:5a8ab28e314c2222de3feaf6dac94a0436a37fc08979d2722c99d2bef2619a9b", + "GraphDriver": { + "Data": { + "LowerDir": "/var/lib/containers/storage/overlay/142c1beadf1bb09fbd929465ec98c9dca3256638220450efb4214727d0d0680e/diff:/var/lib/containers/s", + "MergedDir": "/var/lib/containers/storage/overlay/9aa10191f5bddb59e28508e721fdeb43505e5b395845fa99723ed787878dbfea/merged", + "UpperDir": "/var/lib/containers/storage/overlay/9aa10191f5bddb59e28508e721fdeb43505e5b395845fa99723ed787878dbfea/diff", + "WorkDir": "/var/lib/containers/storage/overlay/9aa10191f5bddb59e28508e721fdeb43505e5b395845fa99723ed787878dbfea/work" + }, + "Name": "overlay" + }, + "History": [ + { + "comment": "from Bitnami with love", + "created": "2019-04-09T22:27:40.659377677Z" + }, + { + "created": "2019-04-09T22:38:53.86336555Z", + "created_by": "/bin/sh -c #(nop) LABEL maintainer=Bitnami <containers@bitnami.com>", + "empty_layer": true + }, + { + "created": "2019-04-09T22:38:54.022778765Z", + "created_by": "/bin/sh -c #(nop) ENV IMAGE_OS=debian-9", + "empty_layer": true + }, + ], + "Id": "ace34da54e4af2145e1ad277005adb235a214e4dfe1114c2db9ab460b840f785", + "Labels": { + "maintainer": "Bitnami <containers@bitnami.com>" + }, + "ManifestType": "application/vnd.docker.distribution.manifest.v1+prettyjws", + "Os": "linux", + "Parent": "", + "RepoDigests": [ + "quay.io/bitnami/wildfly@sha256:5a8ab28e314c2222de3feaf6dac94a0436a37fc08979d2722c99d2bef2619a9b" + ], + "RepoTags": [ + "quay.io/bitnami/wildfly:latest" + ], + "RootFS": { + "Layers": [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ], + "Type": "layers" + }, + "Size": 466180019, + "User": "1001", + "Version": "18.09.3", + "VirtualSize": 466180019 + } + ] +""" + +import json +import re +import shlex + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.containers.podman.plugins.module_utils.podman.common import run_podman_command + + +class PodmanImageManager(object): + + def __init__(self, module, results): + + super(PodmanImageManager, self).__init__() + + self.module = module + self.results = results + self.name = self.module.params.get('name') + self.executable = self.module.get_bin_path(module.params.get('executable'), required=True) + self.tag = self.module.params.get('tag') + self.pull = self.module.params.get('pull') + self.push = self.module.params.get('push') + self.path = self.module.params.get('path') + self.force = self.module.params.get('force') + self.state = self.module.params.get('state') + self.validate_certs = self.module.params.get('validate_certs') + self.auth_file = self.module.params.get('auth_file') + self.username = self.module.params.get('username') + self.password = self.module.params.get('password') + self.ca_cert_dir = self.module.params.get('ca_cert_dir') + self.build = self.module.params.get('build') + self.push_args = self.module.params.get('push_args') + + repo, repo_tag = parse_repository_tag(self.name) + if repo_tag: + self.name = repo + self.tag = repo_tag + + self.image_name = '{name}:{tag}'.format(name=self.name, tag=self.tag) + + if self.state in ['present', 'build']: + self.present() + + if self.state in ['absent']: + self.absent() + + def _run(self, args, expected_rc=0, ignore_errors=False): + return run_podman_command( + module=self.module, + executable=self.executable, + args=args, + expected_rc=expected_rc, + ignore_errors=ignore_errors) + + def _get_id_from_output(self, lines, startswith=None, contains=None, split_on=' ', maxsplit=1): + layer_ids = [] + for line in lines.splitlines(): + if startswith and line.startswith(startswith) or contains and contains in line: + splitline = line.rsplit(split_on, maxsplit) + layer_ids.append(splitline[1]) + + # Podman 1.4 changed the output to only include the layer id when run in quiet mode + if not layer_ids: + layer_ids = lines.splitlines() + + return(layer_ids[-1]) + + def present(self): + image = self.find_image() + + if image: + digest_before = image[0].get('Digest', image[0].get('digest')) + else: + digest_before = None + + if not image or self.force: + if self.path: + # Build the image + self.results['actions'].append('Built image {image_name} from {path}'.format(image_name=self.image_name, path=self.path)) + if not self.module.check_mode: + image = self.results['image'] = self.build_image() + else: + # Pull the image + self.results['actions'].append('Pulled image {image_name}'.format(image_name=self.image_name)) + if not self.module.check_mode: + image = self.results['image'] = self.pull_image() + + if not image: + image = self.find_image() + digest_after = image[0].get('Digest', image[0].get('digest')) + self.results['changed'] = digest_before != digest_after + + if self.push: + # Push the image + if '/' in self.image_name: + push_format_string = 'Pushed image {image_name}' + else: + push_format_string = 'Pushed image {image_name} to {dest}' + self.results['actions'].append(push_format_string.format(image_name=self.image_name, dest=self.push_args['dest'])) + self.results['changed'] = True + if not self.module.check_mode: + self.results['image'] = self.push_image() + + def absent(self): + image = self.find_image() + + if image: + self.results['actions'].append('Removed image {name}'.format(name=self.name)) + self.results['changed'] = True + self.results['image']['state'] = 'Deleted' + if not self.module.check_mode: + self.remove_image() + + def find_image(self, image_name=None): + if image_name is None: + image_name = self.image_name + args = ['image', 'ls', image_name, '--format', 'json'] + rc, images, err = self._run(args, ignore_errors=True) + if len(images) > 0: + return json.loads(images) + else: + return None + + def inspect_image(self, image_name=None): + if image_name is None: + image_name = self.image_name + args = ['inspect', image_name, '--format', 'json'] + rc, image_data, err = self._run(args) + if len(image_data) > 0: + return json.loads(image_data) + else: + return None + + def pull_image(self, image_name=None): + if image_name is None: + image_name = self.image_name + + args = ['pull', image_name, '-q'] + + if self.auth_file: + args.extend(['--authfile', self.auth_file]) + + if self.username and self.password: + cred_string = '{user}:{password}'.format(user=self.username, password=self.password) + args.extend(['--creds', cred_string]) + + if self.validate_certs is not None: + if self.validate_certs: + args.append('--tls-verify') + else: + args.append('--tls-verify=false') + + if self.ca_cert_dir: + args.extend(['--cert-dir', self.ca_cert_dir]) + + rc, out, err = self._run(args, ignore_errors=True) + if rc != 0: + self.module.fail_json(msg='Failed to pull image {image_name}'.format(image_name=image_name)) + return self.inspect_image(out.strip()) + + def build_image(self): + args = ['build', '-q'] + args.extend(['-t', self.image_name]) + + if self.validate_certs is not None: + if self.validate_certs: + args.append('--tls-verify') + else: + args.append('--tls-verify=false') + + annotation = self.build.get('annotation') + if annotation: + for k, v in annotation.items(): + args.extend(['--annotation', '{k}={v}'.format(k=k, v=v)]) + + if self.ca_cert_dir: + args.extend(['--cert-dir', self.ca_cert_dir]) + + if self.build.get('force_rm'): + args.append('--force-rm') + + image_format = self.build.get('format') + if image_format: + args.extend(['--format', image_format]) + + if not self.build.get('cache'): + args.append('--no-cache') + + if self.build.get('rm'): + args.append('--rm') + + volume = self.build.get('volume') + if volume: + for v in volume: + args.extend(['--volume', v]) + + if self.auth_file: + args.extend(['--authfile', self.auth_file]) + + if self.username and self.password: + cred_string = '{user}:{password}'.format(user=self.username, password=self.password) + args.extend(['--creds', cred_string]) + + extra_args = self.build.get('extra_args') + if extra_args: + args.extend([arg for arg in shlex.split(extra_args)]) + + args.append(self.path) + + rc, out, err = self._run(args, ignore_errors=True) + if rc != 0: + self.module.fail_json(msg="Failed to build image {image}: {out} {err}".format(image=self.image_name, out=out, err=err)) + + last_id = self._get_id_from_output(out, startswith='-->') + return self.inspect_image(last_id) + + def push_image(self): + args = ['push'] + + if self.validate_certs is not None: + if self.validate_certs: + args.append('--tls-verify') + else: + args.append('--tls-verify=false') + + if self.ca_cert_dir: + args.extend(['--cert-dir', self.ca_cert_dir]) + + if self.username and self.password: + cred_string = '{user}:{password}'.format(user=self.username, password=self.password) + args.extend(['--creds', cred_string]) + + if self.auth_file: + args.extend(['--authfile', self.auth_file]) + + if self.push_args.get('compress'): + args.append('--compress') + + push_format = self.push_args.get('format') + if push_format: + args.extend(['--format', push_format]) + + if self.push_args.get('remove_signatures'): + args.append('--remove-signatures') + + sign_by_key = self.push_args.get('sign_by') + if sign_by_key: + args.extend(['--sign-by', sign_by_key]) + + args.append(self.image_name) + + # Build the destination argument + dest = self.push_args.get('dest') + dest_format_string = '{dest}/{image_name}' + regexp = re.compile(r'/{name}(:{tag})?'.format(name=self.name, tag=self.tag)) + if not dest: + if '/' not in self.name: + self.module.fail_json(msg="'push_args['dest']' is required when pushing images that do not have the remote registry in the image name") + + # If the push destination contains the image name and/or the tag + # remove it and warn since it's not needed. + elif regexp.search(dest): + dest = regexp.sub('', dest) + self.module.warn("Image name and tag are automatically added to push_args['dest']. Destination changed to {dest}".format(dest=dest)) + + if dest and dest.endswith('/'): + dest = dest[:-1] + + transport = self.push_args.get('transport') + if transport: + if not dest: + self.module.fail_json("'push_args['transport'] requires 'push_args['dest'] but it was not provided.") + if transport == 'docker': + dest_format_string = '{transport}://{dest}' + elif transport == 'ostree': + dest_format_string = '{transport}:{name}@{dest}' + else: + dest_format_string = '{transport}:{dest}' + + dest_string = dest_format_string.format(transport=transport, name=self.name, dest=dest, image_name=self.image_name,) + + # Only append the destination argument if the image name is not a URL + if '/' not in self.name: + args.append(dest_string) + + rc, out, err = self._run(args, ignore_errors=True) + if rc != 0: + self.module.fail_json(msg="Failed to push image {image_name}: {err}".format(image_name=self.image_name, err=err)) + last_id = self._get_id_from_output( + out + err, contains=':', split_on=':') + + return self.inspect_image(last_id) + + def remove_image(self, image_name=None): + if image_name is None: + image_name = self.image_name + + args = ['rmi', image_name] + if self.force: + args.append('--force') + rc, out, err = self._run(args, ignore_errors=True) + if rc != 0: + self.module.fail_json(msg='Failed to remove image {image_name}. {err}'.format(image_name=image_name, err=err)) + return out + + +def parse_repository_tag(repo_name): + parts = repo_name.rsplit('@', 1) + if len(parts) == 2: + return tuple(parts) + parts = repo_name.rsplit(':', 1) + if len(parts) == 2 and '/' not in parts[1]: + return tuple(parts) + return repo_name, None + + +def main(): + module = AnsibleModule( + argument_spec=dict( + name=dict(type='str', required=True), + tag=dict(type='str', default='latest'), + pull=dict(type='bool', default=True), + push=dict(type='bool', default=False), + path=dict(type='str'), + force=dict(type='bool', default=False), + state=dict(type='str', default='present', choices=['absent', 'present', 'build']), + validate_certs=dict(type='bool', default=True, aliases=['tlsverify', 'tls_verify']), + executable=dict(type='str', default='podman'), + auth_file=dict(type='path', aliases=['authfile']), + username=dict(type='str'), + password=dict(type='str', no_log=True), + ca_cert_dir=dict(type='path'), + build=dict( + type='dict', + aliases=['build_args', 'buildargs'], + default={}, + options=dict( + annotation=dict(type='dict'), + force_rm=dict(type='bool', default=False), + format=dict( + type='str', + choices=['oci', 'docker'], + default='oci' + ), + cache=dict(type='bool', default=True), + rm=dict(type='bool', default=True), + volume=dict(type='list', elements='str'), + extra_args=dict(type='str'), + ), + ), + push_args=dict( + type='dict', + default={}, + options=dict( + compress=dict(type='bool'), + format=dict(type='str', choices=['oci', 'v2s1', 'v2s2']), + remove_signatures=dict(type='bool'), + sign_by=dict(type='str'), + dest=dict(type='str', aliases=['destination'],), + transport=dict( + type='str', + choices=[ + 'dir', + 'docker-archive', + 'docker-daemon', + 'oci-archive', + 'ostree', + ] + ), + ), + ), + ), + supports_check_mode=True, + required_together=( + ['username', 'password'], + ), + mutually_exclusive=( + ['authfile', 'username'], + ['authfile', 'password'], + ), + ) + + results = dict( + changed=False, + actions=[], + image={}, + ) + + PodmanImageManager(module, results) + module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_image_info.py b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_image_info.py new file mode 100644 index 00000000..4712c56a --- /dev/null +++ b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_image_info.py @@ -0,0 +1,234 @@ +#!/usr/bin/python +# Copyright (c) 2019 Ansible Project +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = r''' +module: podman_image_info +author: + - Sam Doran (@samdoran) +short_description: Gather info about images using podman +notes: + - Podman may required elevated privileges in order to run properly. +description: + - Gather info about images using C(podman) +options: + executable: + description: + - Path to C(podman) executable if it is not in the C($PATH) on the machine running C(podman) + default: 'podman' + type: str + name: + description: + - List of tags or UID to gather info about. If no name is given return info about all images. + type: list + elements: str + +''' + +EXAMPLES = r""" +- name: Gather info for all images + containers.podman.podman_image_info: + +- name: Gather info on a specific image + containers.podman.podman_image_info: + name: nginx + +- name: Gather info on several images + containers.podman.podman_image_info: + name: + - redis + - quay.io/bitnami/wildfly +""" + +RETURN = r""" +images: + description: info from all or specified images + returned: always + type: dict + sample: [ + { + "Annotations": {}, + "Architecture": "amd64", + "Author": "", + "Comment": "from Bitnami with love", + "ContainerConfig": { + "Cmd": [ + "nami", + "start", + "--foreground", + "wildfly" + ], + "Entrypoint": [ + "/app-entrypoint.sh" + ], + "Env": [ + "PATH=/opt/bitnami/java/bin:/opt/bitnami/wildfly/bin:/opt/bitnami/nami/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "IMAGE_OS=debian-9", + "NAMI_VERSION=0.0.9-0", + "GPG_KEY_SERVERS_LIST=ha.pool.sks-keyservers.net \ +hkp://p80.pool.sks-keyservers.net:80 keyserver.ubuntu.com hkp://keyserver.ubuntu.com:80 pgp.mit.edu", + "TINI_VERSION=v0.13.2", + "TINI_GPG_KEY=595E85A6B1B4779EA4DAAEC70B588DFF0527A9B7", + "GOSU_VERSION=1.10", + "GOSU_GPG_KEY=B42F6819007F00F88E364FD4036A9C25BF357DD4", + "BITNAMI_IMAGE_VERSION=14.0.1-debian-9-r12", + "BITNAMI_APP_NAME=wildfly", + "WILDFLY_JAVA_HOME=", + "WILDFLY_JAVA_OPTS=", + "WILDFLY_MANAGEMENT_HTTP_PORT_NUMBER=9990", + "WILDFLY_PASSWORD=bitnami", + "WILDFLY_PUBLIC_CONSOLE=true", + "WILDFLY_SERVER_AJP_PORT_NUMBER=8009", + "WILDFLY_SERVER_HTTP_PORT_NUMBER=8080", + "WILDFLY_SERVER_INTERFACE=0.0.0.0", + "WILDFLY_USERNAME=user", + "WILDFLY_WILDFLY_HOME=/home/wildfly", + "WILDFLY_WILDFLY_OPTS=-Dwildfly.as.deployment.ondemand=false" + ], + "ExposedPorts": { + "8080/tcp": {}, + "9990/tcp": {} + }, + "Labels": { + "maintainer": "Bitnami <containers@bitnami.com>" + } + }, + "Created": "2018-09-25T04:07:45.934395523Z", + "Digest": "sha256:5c7d8e2dd66dcf4a152a4032a1d3c5a33458c67e1c1335edd8d18d738892356b", + "GraphDriver": { + "Data": { + "LowerDir": "/var/lib/containers/storage/overlay/a9dbf5616cc16919a8ac0dfc60aff87a72b5be52994c4649fcc91a089a12931\ +f/diff:/var/lib/containers/storage/overlay/67129bd46022122a7d8b7acb490092af6c7ce244ce4fbd7d9e2d2b7f5979e090/diff:/var/lib/containers/storage/overlay/7c51242c\ +4c5db5c74afda76d7fdbeab6965d8b21804bb3fc597dee09c770b0ca/diff:/var/lib/containers/storage/overlay/f97315dc58a9c002ba0cabccb9933d4b0d2113733d204188c88d72f75569b57b/diff:/var/lib/containers/storage/overlay/1dbde2dd497ddde2b467727125b900958a051a72561e58d29abe3d660dcaa9a7/diff:/var/lib/containers/storage/overlay/4aad9d80f30c3f0608f58173558b7554d84dee4dc4479672926eca29f75e6e33/diff:/var/lib/containers/storage/overlay/6751fc9b6868254870c062d75a511543fc8cfda2ce6262f4945f107449219632/diff:/var/lib/containers/storage/overlay/a27034d79081347421dd24d7e9e776c18271cd9a6e51053cb39af4d3d9c400e8/diff:/var/lib/containers/storage/overlay/537cf0045ed9cd7989f7944e7393019c81b16c1799a2198d8348cd182665397f/diff:/var/lib/containers/storage/overlay/27578615c5ae352af4e8449862d61aaf5c11b105a7d5905af55bd01b0c656d6e/diff:/var/lib/containers/storage/overlay/566542742840fe3034b3596f7cb9e62a6274c95a69f368f9e713746f8712c0b6/diff", + "MergedDir": "/var/lib/containers/storage/overlay/72bb96d6\ +c53ad57a0b1e44cab226a6251598accbead40b23fac89c19ad8c25ca/merged", + "UpperDir": "/var/lib/containers/storage/overlay/72bb96d6c53ad57a0b1e44cab226a6251598accbead40b23fac89c19ad8c25ca/diff", + "WorkDir": "/var/lib/containers/storage/overlay/72bb96d6c53ad57a0b1e44cab226a6251598accbead40b23fac89c19ad8c25ca/work" + }, + "Name": "overlay" + }, + "Id": "bcacbdf7a119c0fa934661ca8af839e625ce6540d9ceb6827cdd389f823d49e0", + "Labels": { + "maintainer": "Bitnami <containers@bitnami.com>" + }, + "ManifestType": "application/vnd.docker.distribution.manifest.v1+prettyjws", + "Os": "linux", + "Parent": "", + "RepoDigests": [ + "quay.io/bitnami/wildfly@sha256:5c7d8e2dd66dcf4a152a4032a1d3c5a33458c67e1c1335edd8d18d738892356b" + ], + "RepoTags": [ + "quay.io/bitnami/wildfly:latest" + ], + "RootFS": { + "Layers": [ + "sha256:75391df2c87e076b0c2f72d20c95c57dc8be7ee684cc07273416cce622b43367", + "sha256:7dd303f041039bfe8f0833092673ac35f93137d10e0fbc4302021ea65ad57731", + "sha256:720d9edf0cd2a9bb56b88b80be9070dbfaad359514c70094c65066963fed485d", + "sha256:6a567ecbf97725501a634fcb486271999aa4591b633b4ae9932a46b40f5aaf47", + "sha256:59e9a6db8f178f3da868614564faabb2820cdfb69be32e63a4405d6f7772f68c", + "sha256:310a82ccb092cd650215ab375da8943d235a263af9a029b8ac26a281446c04db", + "sha256:36cb91cf4513543a8f0953fed785747ea18b675bc2677f3839889cfca0aac79e" + ], + "Type": "layers" + }, + "Size": 569919342, + "User": "", + "Version": "17.06.0-ce", + "VirtualSize": 569919342 + } + ] +""" + +import json + +from ansible.module_utils.basic import AnsibleModule + + +def image_exists(module, executable, name): + command = [executable, 'image', 'exists', name] + rc, out, err = module.run_command(command) + if rc == 1: + return False + elif 'Command "exists" not found' in err: + # The 'exists' test is available in podman >= 0.12.1 + command = [executable, 'image', 'ls', '-q', name] + rc2, out2, err2 = module.run_command(command) + if rc2 != 0: + return False + return True + + +def filter_invalid_names(module, executable, name): + valid_names = [] + names = name + if not isinstance(name, list): + names = [name] + + for name in names: + if image_exists(module, executable, name): + valid_names.append(name) + + return valid_names + + +def get_image_info(module, executable, name): + names = name + if not isinstance(name, list): + names = [name] + + if len(names) > 0: + command = [executable, 'image', 'inspect'] + command.extend(names) + rc, out, err = module.run_command(command) + + if rc != 0: + module.fail_json(msg="Unable to gather info for '{0}': {1}".format(', '.join(names), err)) + return out + + else: + return json.dumps([]) + + +def get_all_image_info(module, executable): + command = [executable, 'image', 'ls', '-q'] + rc, out, err = module.run_command(command) + name = out.strip().split('\n') + out = get_image_info(module, executable, name) + + return out + + +def main(): + module = AnsibleModule( + argument_spec=dict( + executable=dict(type='str', default='podman'), + name=dict(type='list', elements='str') + ), + supports_check_mode=True, + ) + + executable = module.params['executable'] + name = module.params.get('name') + executable = module.get_bin_path(executable, required=True) + + if name: + valid_names = filter_invalid_names(module, executable, name) + results = json.loads(get_image_info(module, executable, valid_names)) + else: + results = json.loads(get_all_image_info(module, executable)) + + results = dict( + changed=False, + images=results + ) + + module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_login_info.py b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_login_info.py new file mode 100644 index 00000000..0ff72e43 --- /dev/null +++ b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_login_info.py @@ -0,0 +1,117 @@ +#!/usr/bin/python +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = r""" +module: podman_login_info +author: + - "Clemens Lange (@clelange)" +version_added: '1.0.0' +short_description: Return the logged-in user if any for a given registry +notes: [] +description: + - Return the logged-in user if any for a given registry. +requirements: + - "Podman installed on host" +options: + registry: + description: + - Registry server. + type: str + required: true + authfile: + description: + - Path of the authentication file. Default is + ``${XDG_RUNTIME_DIR}/containers/auth.json`` + (Not available for remote commands) You can also override the default + path of the authentication file by setting the ``REGISTRY_AUTH_FILE`` + environment variable. ``export REGISTRY_AUTH_FILE=path`` + type: path + executable: + description: + - Path to C(podman) executable if it is not in the C($PATH) on the + machine running C(podman) + default: 'podman' + type: str +""" + +EXAMPLES = r""" +- name: Return the logged-in user for docker hub registry + containers.podman.podman_login_info: + registry: docker.io + +- name: Return the logged-in user for quay.io registry + containers.podman.podman_login_info: + registry: quay.io +""" + +RETURN = r""" +login: + description: Logged in user for a registry + returned: always + type: dict + sample: { + "logged_in": true, + "registry": "docker.io", + "username": "clelange", + } +""" + +import json +from ansible.module_utils.basic import AnsibleModule + + +def get_login_info(module, executable, authfile, registry): + command = [executable, 'login', '--get-login'] + result = dict( + registry=registry, + username='', + logged_in=False, + ) + if authfile: + command.extend(['--authfile', authfile]) + if registry: + command.append(registry) + rc, out, err = module.run_command(command) + if rc != 0: + if 'Error: not logged into' in err: + # The error message is e.g. 'Error: not logged into docker.io' + # Therefore get last word to extract registry name + result["registry"] = err.split()[-1] + err = '' + return result + module.fail_json(msg="Unable to gather info for %s: %s" % (registry, err)) + result["username"] = out.strip() + result["logged_in"] = True + return result + + +def main(): + module = AnsibleModule( + argument_spec=dict( + executable=dict(type='str', default='podman'), + authfile=dict(type='path'), + registry=dict(type='str', required=True) + ), + supports_check_mode=True, + ) + + registry = module.params['registry'] + authfile = module.params['authfile'] + executable = module.get_bin_path(module.params['executable'], required=True) + + inspect_results = get_login_info(module, executable, authfile, registry) + + results = { + "changed": False, + "login": inspect_results, + } + + module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_logout.py b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_logout.py new file mode 100644 index 00000000..35627e8e --- /dev/null +++ b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_logout.py @@ -0,0 +1,154 @@ +#!/usr/bin/python +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = r''' +module: podman_logout +author: + - "Clemens Lange (@clelange)" +short_description: Log out of a container registry using podman +notes: [] +description: + - Log out of a container registry server using the podman logout command + by deleting the cached credentials stored in the `auth.json` file. + If the registry is not specified, the first registry under + `[registries.search]` from `registries.conf `will be used. The path of + the authentication file can be overridden by the user by setting the + `authfile` flag. The default path used is + `${XDG_RUNTIME_DIR}/containers/auth.json`. + All the cached credentials can be removed by setting the `all` flag. + Warning - podman will use credentials in `${HOME}/.docker/config.json` + to authenticate in case they are not found in the default `authfile`. + However, the logout command will only removed credentials in the + `authfile` specified. +requirements: + - "Podman installed on host" +options: + registry: + description: + - Registry server. If the registry is not specified, + the first registry under `[registries.search]` from + `registries.conf` will be used. + type: str + authfile: + description: + - Path of the authentication file. Default is + ``${XDG_RUNTIME_DIR}/containers/auth.json`` + You can also override the default path of the authentication + file by setting the ``REGISTRY_AUTH_FILE`` environment + variable. ``export REGISTRY_AUTH_FILE=path`` + type: path + all: + description: + - Remove the cached credentials for all registries in the auth file. + type: bool + ignore_docker_credentials: + description: + - Credentials created using other tools such as `docker login` are not + removed unless the corresponding `authfile` is explicitly specified. + Since podman also uses existing credentials in these files by default + (for docker e.g. `${HOME}/.docker/config.json`), module execution will + fail if a docker login exists for the registry specified in any + `authfile` is used by podman. This can be ignored by setting + `ignore_docker_credentials` to `yes` - the credentials will be kept and + `changed` will be false. + This option cannot be used together with `all` since in this case + podman will not check for existing `authfiles` created by other tools. + type: bool + executable: + description: + - Path to C(podman) executable if it is not in the C($PATH) on the + machine running C(podman) + default: 'podman' + type: str +''' + +EXAMPLES = r""" +- name: Log out of default registry + podman_logout: + +- name: Log out of quay.io + podman_logout: + registry: quay.io + +- name: Log out of all registries in auth file + podman_logout: + all: yes + +- name: Log out of all registries in specified auth file + podman_logout: + authfile: $HOME/.docker/config.json + all: yes +""" +# noqa: F402 + +import json # noqa: F402 +from ansible.module_utils.basic import AnsibleModule + + +def logout(module, executable, registry, authfile, all_registries, ignore_docker_credentials): + command = [executable, 'logout'] + changed = False + if authfile: + command.extend(['--authfile', authfile]) + if registry: + command.append(registry) + if all_registries: + command.append("--all") + rc, out, err = module.run_command(command) + if rc != 0: + if 'Error: Not logged into' not in err: + module.fail_json(msg="Unable to gather info for %s: %s" % (registry, err)) + else: + # If the command is successful, we managed to log out + # Mind: This also applied if --all flag is used, while in this case + # there is no check whether one has been logged into any registry + changed = True + if 'Existing credentials were established via' in out: + # The command will return successfully but not log out the user if the + # credentials were initially created using docker. Catch this behaviour: + if not ignore_docker_credentials: + module.fail_json(msg="Unable to log out of %s: %s" % (registry, out)) + else: + changed = False + return changed, out, err + + +def main(): + module = AnsibleModule( + argument_spec=dict( + executable=dict(type='str', default='podman'), + registry=dict(type='str'), + authfile=dict(type='path'), + all=dict(type='bool'), + ignore_docker_credentials=dict(type='bool'), + ), + supports_check_mode=True, + mutually_exclusive=( + ['registry', 'all'], + ['ignore_docker_credentials', 'all'], + ), + ) + + registry = module.params['registry'] + authfile = module.params['authfile'] + all_registries = module.params['all'] + ignore_docker_credentials = module.params['ignore_docker_credentials'] + executable = module.get_bin_path(module.params['executable'], required=True) + + changed, out, err = logout(module, executable, registry, authfile, + all_registries, ignore_docker_credentials) + + results = { + "changed": changed, + "stdout": out, + "stderr": err, + } + + module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_network.py b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_network.py new file mode 100644 index 00000000..590b4690 --- /dev/null +++ b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_network.py @@ -0,0 +1,614 @@ +#!/usr/bin/python +# Copyright (c) 2020 Red Hat +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = r""" +module: podman_network +author: + - "Sagi Shnaidman (@sshnaidm)" +version_added: '1.0.0' +short_description: Manage podman networks +notes: [] +description: + - Manage podman networks with podman network command. +requirements: + - podman +options: + name: + description: + - Name of the network + type: str + required: True + executable: + description: + - Path to C(podman) executable if it is not in the C($PATH) on the + machine running C(podman) + default: 'podman' + type: str + disable_dns: + description: + - disable dns plugin (default "false") + type: bool + driver: + description: + - Driver to manage the network (default "bridge") + type: str + gateway: + description: + - IPv4 or IPv6 gateway for the subnet + type: str + internal: + description: + - Restrict external access from this network (default "false") + type: bool + ip_range: + description: + - Allocate container IP from range + type: str + ipv6: + description: + - Enable IPv6 (Dual Stack) networking. You must pass a IPv6 subnet. + The subnet option must be used with the ipv6 option. + type: bool + subnet: + description: + - Subnet in CIDR format + type: str + macvlan: + description: + - Create a Macvlan connection based on this device + type: str + opt: + description: + - Add network options. Currently 'vlan' and 'mtu' are supported. + type: dict + suboptions: + mtu: + description: + - MTU size for bridge network interface. + type: int + required: false + vlan: + description: + - VLAN tag for bridge which enables vlan_filtering. + type: int + required: false + debug: + description: + - Return additional information which can be helpful for investigations. + type: bool + default: False + state: + description: + - State of network, default 'present' + type: str + default: present + choices: + - present + - absent + recreate: + description: + - Recreate network even if exists. + type: bool + default: false +""" + +EXAMPLES = r""" +- name: Create a podman network + containers.podman.podman_network: + name: podman_network + become: true + +- name: Create internal podman network + containers.podman.podman_network: + name: podman_internal + internal: true + ip_range: 192.168.22.128/25 + subnet: 192.168.22.0/24 + gateway: 192.168.22.1 + become: true +""" + +RETURN = r""" +network: + description: Facts from created or updated networks + returned: always + type: list + sample: [ + { + "cniVersion": "0.4.0", + "name": "podman", + "plugins": [ + { + "bridge": "cni-podman0", + "ipMasq": true, + "ipam": { + "ranges": [ + [ + { + "gateway": "10.88.0.1", + "subnet": "10.88.0.0/16" + } + ] + ], + "routes": [ + { + "dst": "0.0.0.0/0" + } + ], + "type": "host-local" + }, + "isGateway": true, + "type": "bridge" + }, + { + "capabilities": { + "portMappings": true + }, + "type": "portmap" + }, + { + "backend": "iptables", + "type": "firewall" + } + ] + } + ] +""" + +import json # noqa: F402 +from distutils.version import LooseVersion # noqa: F402 +import os # noqa: F402 + +from ansible.module_utils.basic import AnsibleModule # noqa: F402 +from ansible.module_utils._text import to_bytes, to_native # noqa: F402 + +from ansible_collections.containers.podman.plugins.module_utils.podman.common import lower_keys + + +class PodmanNetworkModuleParams: + """Creates list of arguments for podman CLI command. + + Arguments: + action {str} -- action type from 'create', 'delete' + params {dict} -- dictionary of module parameters + + """ + + def __init__(self, action, params, podman_version, module): + self.params = params + self.action = action + self.podman_version = podman_version + self.module = module + + def construct_command_from_params(self): + """Create a podman command from given module parameters. + + Returns: + list -- list of byte strings for Popen command + """ + if self.action in ['delete']: + return self._simple_action() + if self.action in ['create']: + return self._create_action() + + def _simple_action(self): + if self.action == 'delete': + cmd = ['rm', '-f', self.params['name']] + return [to_bytes(i, errors='surrogate_or_strict') for i in cmd] + + def _create_action(self): + cmd = [self.action, self.params['name']] + all_param_methods = [func for func in dir(self) + if callable(getattr(self, func)) + and func.startswith("addparam")] + params_set = (i for i in self.params if self.params[i] is not None) + for param in params_set: + func_name = "_".join(["addparam", param]) + if func_name in all_param_methods: + cmd = getattr(self, func_name)(cmd) + return [to_bytes(i, errors='surrogate_or_strict') for i in cmd] + + def check_version(self, param, minv=None, maxv=None): + if minv and LooseVersion(minv) > LooseVersion( + self.podman_version): + self.module.fail_json(msg="Parameter %s is supported from podman " + "version %s only! Current version is %s" % ( + param, minv, self.podman_version)) + if maxv and LooseVersion(maxv) < LooseVersion( + self.podman_version): + self.module.fail_json(msg="Parameter %s is supported till podman " + "version %s only! Current version is %s" % ( + param, minv, self.podman_version)) + + def addparam_gateway(self, c): + return c + ['--gateway', self.params['gateway']] + + def addparam_driver(self, c): + return c + ['--driver', self.params['driver']] + + def addparam_subnet(self, c): + return c + ['--subnet', self.params['subnet']] + + def addparam_ip_range(self, c): + return c + ['--ip-range', self.params['ip_range']] + + def addparam_ipv6(self, c): + return c + ['--ipv6'] + + def addparam_macvlan(self, c): + return c + ['--macvlan', self.params['macvlan']] + + def addparam_internal(self, c): + return c + ['--internal=%s' % self.params['internal']] + + def addparam_opt(self, c): + for opt in self.params['opt'].items(): + c += ['--opt', + b"=".join([to_bytes(k, errors='surrogate_or_strict') + for k in opt])] + return c + + def addparam_disable_dns(self, c): + return c + ['--disable-dns=%s' % self.params['disable_dns']] + + +class PodmanNetworkDefaults: + def __init__(self, module, podman_version): + self.module = module + self.version = podman_version + self.defaults = { + 'driver': 'bridge', + 'disable_dns': False, + 'internal': False, + } + + def default_dict(self): + # make here any changes to self.defaults related to podman version + return self.defaults + + +class PodmanNetworkDiff: + def __init__(self, module, info, podman_version): + self.module = module + self.version = podman_version + self.default_dict = None + self.info = lower_keys(info) + self.params = self.defaultize() + self.diff = {'before': {}, 'after': {}} + self.non_idempotent = {} + + def defaultize(self): + params_with_defaults = {} + self.default_dict = PodmanNetworkDefaults( + self.module, self.version).default_dict() + for p in self.module.params: + if self.module.params[p] is None and p in self.default_dict: + params_with_defaults[p] = self.default_dict[p] + else: + params_with_defaults[p] = self.module.params[p] + return params_with_defaults + + def _diff_update_and_compare(self, param_name, before, after): + if before != after: + self.diff['before'].update({param_name: before}) + self.diff['after'].update({param_name: after}) + return True + return False + + def diffparam_disable_dns(self): + dns_installed = False + for f in [ + '/usr/libexec/cni/dnsname', + '/usr/lib/cni/dnsname', + '/opt/cni/bin/dnsname', + '/opt/bridge/bin/dnsname' + ]: + if os.path.exists(f): + dns_installed = True + before = not bool( + [k for k in self.info['plugins'] if 'domainname' in k]) + after = self.params['disable_dns'] + # If dnsname plugin is not installed, default is disable_dns=True + if not dns_installed and self.module.params['disable_dns'] is None: + after = True + return self._diff_update_and_compare('disable_dns', before, after) + + def diffparam_driver(self): + # Currently only bridge is supported + before = after = 'bridge' + return self._diff_update_and_compare('driver', before, after) + + def diffparam_gateway(self): + try: + before = self.info['plugins'][0]['ipam']['ranges'][0][0]['gateway'] + except (IndexError, KeyError): + before = '' + after = before + if self.params['gateway'] is not None: + after = self.params['gateway'] + return self._diff_update_and_compare('gateway', before, after) + + def diffparam_internal(self): + try: + before = not self.info['plugins'][0]['isgateway'] + except (IndexError, KeyError): + before = False + after = self.params['internal'] + return self._diff_update_and_compare('internal', before, after) + + def diffparam_ip_range(self): + # TODO(sshnaidm): implement IP to CIDR convert and vice versa + before = after = '' + return self._diff_update_and_compare('ip_range', before, after) + + def diffparam_subnet(self): + try: + before = self.info['plugins'][0]['ipam']['ranges'][0][0]['subnet'] + except (IndexError, KeyError): + before = '' + after = before + if self.params['subnet'] is not None: + after = self.params['subnet'] + return self._diff_update_and_compare('subnet', before, after) + + def diffparam_macvlan(self): + before = after = '' + return self._diff_update_and_compare('macvlan', before, after) + + def diffparam_opt(self): + vlan_before = self.info['plugins'][0].get('vlan') + vlan_after = self.params['opt'].get('vlan') if self.params['opt'] else None + if vlan_before or vlan_after: + before, after = {'vlan': vlan_before}, {'vlan': vlan_after} + else: + before, after = {}, {} + mtu_before = self.info['plugins'][0].get('mtu') + mtu_after = self.params['opt'].get('mtu') if self.params['opt'] else None + if mtu_before or mtu_after: + before.update({'mtu': mtu_before}) + after.update({'mtu': mtu_after}) + return self._diff_update_and_compare('opt', before, after) + + def is_different(self): + diff_func_list = [func for func in dir(self) + if callable(getattr(self, func)) and func.startswith( + "diffparam")] + fail_fast = not bool(self.module._diff) + different = False + for func_name in diff_func_list: + dff_func = getattr(self, func_name) + if dff_func(): + if fail_fast: + return True + different = True + # Check non idempotent parameters + for p in self.non_idempotent: + if self.module.params[p] is not None and self.module.params[p] not in [{}, [], '']: + different = True + return different + + +class PodmanNetwork: + """Perform network tasks. + + Manages podman network, inspects it and checks its current state + """ + + def __init__(self, module, name): + """Initialize PodmanNetwork class. + + Arguments: + module {obj} -- ansible module object + name {str} -- name of network + """ + + super(PodmanNetwork, self).__init__() + self.module = module + self.name = name + self.stdout, self.stderr = '', '' + self.info = self.get_info() + self.version = self._get_podman_version() + self.diff = {} + self.actions = [] + + @property + def exists(self): + """Check if network exists.""" + return bool(self.info != {}) + + @property + def different(self): + """Check if network is different.""" + diffcheck = PodmanNetworkDiff( + self.module, + self.info, + self.version) + is_different = diffcheck.is_different() + diffs = diffcheck.diff + if self.module._diff and is_different and diffs['before'] and diffs['after']: + self.diff['before'] = "\n".join( + ["%s - %s" % (k, v) for k, v in sorted( + diffs['before'].items())]) + "\n" + self.diff['after'] = "\n".join( + ["%s - %s" % (k, v) for k, v in sorted( + diffs['after'].items())]) + "\n" + return is_different + + def get_info(self): + """Inspect network and gather info about it.""" + # pylint: disable=unused-variable + rc, out, err = self.module.run_command( + [self.module.params['executable'], b'network', b'inspect', self.name]) + return json.loads(out)[0] if rc == 0 else {} + + def _get_podman_version(self): + # pylint: disable=unused-variable + rc, out, err = self.module.run_command( + [self.module.params['executable'], b'--version']) + if rc != 0 or not out or "version" not in out: + self.module.fail_json(msg="%s run failed!" % + self.module.params['executable']) + return out.split("version")[1].strip() + + def _perform_action(self, action): + """Perform action with network. + + Arguments: + action {str} -- action to perform - create, stop, delete + """ + b_command = PodmanNetworkModuleParams(action, + self.module.params, + self.version, + self.module, + ).construct_command_from_params() + full_cmd = " ".join([self.module.params['executable'], 'network'] + + [to_native(i) for i in b_command]) + self.module.log("PODMAN-NETWORK-DEBUG: %s" % full_cmd) + self.actions.append(full_cmd) + if not self.module.check_mode: + rc, out, err = self.module.run_command( + [self.module.params['executable'], b'network'] + b_command, + expand_user_and_vars=False) + self.stdout = out + self.stderr = err + if rc != 0: + self.module.fail_json( + msg="Can't %s network %s" % (action, self.name), + stdout=out, stderr=err) + + def delete(self): + """Delete the network.""" + self._perform_action('delete') + + def create(self): + """Create the network.""" + self._perform_action('create') + + def recreate(self): + """Recreate the network.""" + self.delete() + self.create() + + +class PodmanNetworkManager: + """Module manager class. + + Defines according to parameters what actions should be applied to network + """ + + def __init__(self, module): + """Initialize PodmanManager class. + + Arguments: + module {obj} -- ansible module object + """ + + super(PodmanNetworkManager, self).__init__() + + self.module = module + self.results = { + 'changed': False, + 'actions': [], + 'network': {}, + } + self.name = self.module.params['name'] + self.executable = \ + self.module.get_bin_path(self.module.params['executable'], + required=True) + self.state = self.module.params['state'] + self.recreate = self.module.params['recreate'] + self.network = PodmanNetwork(self.module, self.name) + + def update_network_result(self, changed=True): + """Inspect the current network, update results with last info, exit. + + Keyword Arguments: + changed {bool} -- whether any action was performed + (default: {True}) + """ + facts = self.network.get_info() if changed else self.network.info + out, err = self.network.stdout, self.network.stderr + self.results.update({'changed': changed, 'network': facts, + 'podman_actions': self.network.actions}, + stdout=out, stderr=err) + if self.network.diff: + self.results.update({'diff': self.network.diff}) + if self.module.params['debug']: + self.results.update({'podman_version': self.network.version}) + self.module.exit_json(**self.results) + + def execute(self): + """Execute the desired action according to map of actions & states.""" + states_map = { + 'present': self.make_present, + 'absent': self.make_absent, + } + process_action = states_map[self.state] + process_action() + self.module.fail_json(msg="Unexpected logic error happened, " + "please contact maintainers ASAP!") + + def make_present(self): + """Run actions if desired state is 'started'.""" + if not self.network.exists: + self.network.create() + self.results['actions'].append('created %s' % self.network.name) + self.update_network_result() + elif self.recreate or self.network.different: + self.network.recreate() + self.results['actions'].append('recreated %s' % + self.network.name) + self.update_network_result() + else: + self.update_network_result(changed=False) + + def make_absent(self): + """Run actions if desired state is 'absent'.""" + if not self.network.exists: + self.results.update({'changed': False}) + elif self.network.exists: + self.network.delete() + self.results['actions'].append('deleted %s' % self.network.name) + self.results.update({'changed': True}) + self.results.update({'network': {}, + 'podman_actions': self.network.actions}) + self.module.exit_json(**self.results) + + +def main(): + module = AnsibleModule( + argument_spec=dict( + state=dict(type='str', default="present", + choices=['present', 'absent']), + name=dict(type='str', required=True), + disable_dns=dict(type='bool', required=False), + driver=dict(type='str', required=False), + gateway=dict(type='str', required=False), + internal=dict(type='bool', required=False), + ip_range=dict(type='str', required=False), + ipv6=dict(type='bool', required=False), + subnet=dict(type='str', required=False), + macvlan=dict(type='str', required=False), + opt=dict(type='dict', required=False, + options=dict( + mtu=dict(type='int', required=False), + vlan=dict(type='int', required=False))), + executable=dict(type='str', required=False, default='podman'), + debug=dict(type='bool', default=False), + recreate=dict(type='bool', default=False), + ), + required_by=dict( # for IP range and GW to set 'subnet' is required + ip_range=('subnet'), + gateway=('subnet'), + )) + + PodmanNetworkManager(module).execute() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_network_info.py b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_network_info.py new file mode 100644 index 00000000..a9e18cd4 --- /dev/null +++ b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_network_info.py @@ -0,0 +1,138 @@ +#!/usr/bin/python +# Copyright (c) 2020 Red Hat +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = r""" +module: podman_network_info +author: + - "Sagi Shnaidman (@sshnaidm)" +version_added: '1.0.0' +short_description: Gather info about podman networks +notes: [] +description: + - Gather info about podman networks with podman inspect command. +requirements: + - "Podman installed on host" +options: + name: + description: + - Name of the network + type: str + executable: + description: + - Path to C(podman) executable if it is not in the C($PATH) on the + machine running C(podman) + default: 'podman' + type: str +""" + +EXAMPLES = r""" +- name: Gather info about all present networks + containers.podman.podman_network_info: + +- name: Gather info about specific network + containers.podman.podman_network_info: + name: podman +""" + +RETURN = r""" +networks: + description: Facts from all or specified networks + returned: always + type: list + sample: [ + { + "cniVersion": "0.4.0", + "name": "podman", + "plugins": [ + { + "bridge": "cni-podman0", + "ipMasq": true, + "ipam": { + "ranges": [ + [ + { + "gateway": "10.88.0.1", + "subnet": "10.88.0.0/16" + } + ] + ], + "routes": [ + { + "dst": "0.0.0.0/0" + } + ], + "type": "host-local" + }, + "isGateway": true, + "type": "bridge" + }, + { + "capabilities": { + "portMappings": true + }, + "type": "portmap" + }, + { + "backend": "iptables", + "type": "firewall" + } + ] + } + ] +""" + +import json +from ansible.module_utils.basic import AnsibleModule + + +def get_network_info(module, executable, name): + command = [executable, 'network', 'inspect'] + if not name: + all_names = [executable, 'network', 'ls', '-q'] + rc, out, err = module.run_command(all_names) + if rc != 0: + module.fail_json(msg="Unable to get list of networks: %s" % err) + name = out.split() + if not name: + return [], out, err + command += name + else: + command.append(name) + rc, out, err = module.run_command(command) + if rc != 0 or 'unable to find network configuration' in err: + module.fail_json(msg="Unable to gather info for %s: %s" % (name, err)) + if not out or json.loads(out) is None: + return [], out, err + return json.loads(out), out, err + + +def main(): + module = AnsibleModule( + argument_spec=dict( + executable=dict(type='str', default='podman'), + name=dict(type='str') + ), + supports_check_mode=True, + ) + + name = module.params['name'] + executable = module.get_bin_path(module.params['executable'], required=True) + + inspect_results, out, err = get_network_info(module, executable, name) + + results = { + "changed": False, + "networks": inspect_results, + "stderr": err + } + + module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_pod.py b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_pod.py new file mode 100644 index 00000000..01dbc627 --- /dev/null +++ b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_pod.py @@ -0,0 +1,232 @@ +#!/usr/bin/python +# Copyright (c) 2020 Red Hat +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +# flake8: noqa: E501 +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: podman_pod +short_description: Manage Podman pods +author: + - "Sagi Shnaidman (@sshnaidm)" +version_added: '1.0.0' +description: + - Manage podman pods. +options: + state: + description: + - This variable is set for state + type: str + default: created + choices: + - created + - killed + - restarted + - absent + - started + - stopped + - paused + - unpaused + recreate: + description: + - Use with present and started states to force the re-creation of an + existing pod. + type: bool + default: False + add_host: + description: + - Add a host to the /etc/hosts file shared between all containers in the pod. + type: list + elements: str + required: false + cgroup_parent: + description: + - Path to cgroups under which the cgroup for the pod will be created. If the path + is not absolute, he path is considered to be relative to the cgroups path of the + init process. Cgroups will be created if they do not already exist. + type: str + required: false + dns: + description: + - Set custom DNS servers in the /etc/resolv.conf file that will be shared between + all containers in the pod. A special option, "none" is allowed which disables + creation of /etc/resolv.conf for the pod. + type: list + elements: str + required: false + dns_opt: + description: + - Set custom DNS options in the /etc/resolv.conf file that will be shared between + all containers in the pod. + type: list + elements: str + required: false + dns_search: + description: + - Set custom DNS search domains in the /etc/resolv.conf file that will be shared + between all containers in the pod. + type: list + elements: str + required: false + hostname: + description: + - Set a hostname to the pod + type: str + required: false + infra: + description: + - Create an infra container and associate it with the pod. An infra container is + a lightweight container used to coordinate the shared kernel namespace of a pod. + Default is true. + type: bool + required: false + infra_conmon_pidfile: + description: + - Write the pid of the infra container's conmon process to a file. As conmon runs + in a separate process than Podman, this is necessary when using systemd to manage + Podman containers and pods. + type: str + required: false + infra_command: + description: + - The command that will be run to start the infra container. Default is "/pause". + type: str + required: false + infra_image: + description: + - The image that will be created for the infra container. Default is "k8s.gcr.io/pause:3.1". + type: str + required: false + ip: + description: + - Set a static IP for the pod's shared network. + type: str + required: false + label: + description: + - Add metadata to a pod, pass dictionary of label keys and values. + type: dict + required: false + label_file: + description: + - Read in a line delimited file of labels. + type: str + required: false + mac_address: + description: + - Set a static MAC address for the pod's shared network. + type: str + required: false + name: + description: + - Assign a name to the pod. + type: str + required: true + network: + description: + - Set network mode for the pod. Supported values are bridge (the default), host + (do not create a network namespace, all containers in the pod will use the host's + network), or a comma-separated list of the names of CNI networks the pod should + join. + type: str + required: false + no_hosts: + description: + - Disable creation of /etc/hosts for the pod. + type: bool + required: false + pod_id_file: + description: + - Write the pod ID to the file. + type: str + required: false + publish: + description: + - Publish a port or range of ports from the pod to the host. + type: list + elements: str + required: false + aliases: + - ports + share: + description: + - A comma delimited list of kernel namespaces to share. If none or "" is specified, + no namespaces will be shared. The namespaces to choose from are ipc, net, pid, + user, uts. + type: str + required: false + executable: + description: + - Path to C(podman) executable if it is not in the C($PATH) on the + machine running C(podman) + default: 'podman' + type: str + debug: + description: + - Return additional information which can be helpful for investigations. + type: bool + default: False + +requirements: + - "podman" + +''' + +RETURN = ''' +pod: + description: Pod inspection results for the given pod + built. + returned: always + type: dict + sample: + Config: + cgroupParent: /libpod_parent + created: '2020-06-14T15:16:12.230818767+03:00' + hostname: newpod + id: a5a5c6cdf8c72272fc5c33f787e8d7501e2fa0c1e92b2b602860defdafeeec58 + infraConfig: + infraPortBindings: null + makeInfraContainer: true + labels: {} + lockID: 515 + name: newpod + sharesCgroup: true + sharesIpc: true + sharesNet: true + sharesUts: true + Containers: + - id: dc70a947c7ae15198ec38b3c817587584085dee3919cbeb9969e3ab77ba10fd2 + state: configured + State: + cgroupPath: /libpod_parent/a5a5c6cdf8c72272fc5c33f787e8d7501e2fa0c1e92b2b602860defdafeeec58 + infraContainerID: dc70a947c7ae15198ec38b3c817587584085dee3919cbeb9969e3ab77ba10fd2 + status: Created + +''' + +EXAMPLES = ''' +# What modules does for example +- podman_pod: + name: pod1 + state: started + ports: + - 4444:5555 +''' +from ansible.module_utils.basic import AnsibleModule # noqa: F402 +from ..module_utils.podman.podman_pod_lib import PodmanPodManager # noqa: F402 +from ..module_utils.podman.podman_pod_lib import ARGUMENTS_SPEC_POD # noqa: F402 + + +def main(): + module = AnsibleModule( + argument_spec=ARGUMENTS_SPEC_POD + ) + results = PodmanPodManager(module, module.params).execute() + module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_pod_info.py b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_pod_info.py new file mode 100644 index 00000000..c02e503c --- /dev/null +++ b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_pod_info.py @@ -0,0 +1,145 @@ +#!/usr/bin/python +# Copyright (c) 2020 Red Hat +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = r""" +module: podman_pod_info +author: + - "Sagi Shnaidman (@sshnaidm)" +version_added: '1.0.0' +short_description: Gather info about podman pods +notes: [] +description: + - Gather info about podman pods with podman inspect command. +requirements: + - "Podman installed on host" +options: + name: + description: + - Name of the pod + type: str + executable: + description: + - Path to C(podman) executable if it is not in the C($PATH) on the + machine running C(podman) + default: 'podman' + type: str +""" + +EXAMPLES = r""" +- name: Gather info about all present pods + containers.podman.podman_pod_info: + +- name: Gather info about specific pods + containers.podman.podman_pod_info: + name: special_pod +""" + +RETURN = r""" +pods: + description: Facts from all or specified pods + returned: always + type: list + sample: [ + { + "Config": { + "id": "d9cb6dbb0....", + "name": "pod1", + "hostname": "pod1host", + "labels": { + }, + "cgroupParent": "/libpod_parent", + "sharesCgroup": true, + "sharesIpc": true, + "sharesNet": true, + "sharesUts": true, + "infraConfig": { + "makeInfraContainer": true, + "infraPortBindings": [ + { + "hostPort": 7777, + "containerPort": 7111, + "protocol": "tcp", + "hostIP": "" + } + ] + }, + "created": "2020-07-13T20:29:12.572282186+03:00", + "lockID": 682 + }, + "State": { + "cgroupPath": "/libpod_parent/d9cb6dbb0....", + "infraContainerID": "ad46737bf....", + "status": "Created" + }, + "Containers": [ + { + "id": "ad46737bf....", + "state": "configured" + } + ] + } + ] +""" + +import json +from ansible.module_utils.basic import AnsibleModule + + +def get_pod_info(module, executable, name): + command = [executable, 'pod', 'inspect'] + pods = [name] + result = [] + errs = [] + rcs = [] + if not name: + all_names = [executable, 'pod', 'ls', '-q'] + rc, out, err = module.run_command(all_names) + if rc != 0: + module.fail_json(msg="Unable to get list of pods: %s" % err) + name = out.split() + if not name: + return [], out, err + pods = name + for pod in pods: + rc, out, err = module.run_command(command + [pod]) + errs.append(err.strip()) + rcs += [rc] + if not out or json.loads(out) is None: + continue + result.append(json.loads(out)) + return result, errs, rcs + + +def main(): + module = AnsibleModule( + argument_spec=dict( + executable=dict(type='str', default='podman'), + name=dict(type='str') + ), + supports_check_mode=True, + ) + + name = module.params['name'] + executable = module.get_bin_path(module.params['executable'], required=True) + + inspect_results, errs, rcs = get_pod_info(module, executable, name) + + if len(rcs) > 1 and 0 not in rcs: + module.fail_json(msg="Failed to inspect pods", stderr="\n".join(errs)) + + results = { + "changed": False, + "pods": inspect_results, + "stderr": "\n".join(errs), + } + + module.exit_json(**results) + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_volume.py b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_volume.py new file mode 100644 index 00000000..d74e39d3 --- /dev/null +++ b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_volume.py @@ -0,0 +1,477 @@ +#!/usr/bin/python +# Copyright (c) 2020 Red Hat +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +# flake8: noqa: E501 +from __future__ import absolute_import, division, print_function +__metaclass__ = type + +DOCUMENTATION = ''' +--- +module: podman_volume +short_description: Manage Podman volumes +author: + - "Sagi Shnaidman (@sshnaidm)" +version_added: '1.1.0' +description: + - Manage Podman volumes +options: + state: + description: + - State of volume, default 'present' + type: str + default: present + choices: + - present + - absent + recreate: + description: + - Recreate volume even if exists. + type: bool + default: false + name: + description: + - Name of volume. + type: str + required: true + label: + description: + - Add metadata to a pod volume (e.g., label com.example.key=value). + type: dict + required: false + driver: + description: + - Specify volume driver name (default local). + type: str + required: false + options: + description: + - Set driver specific options. For example 'device=tpmfs', 'type=tmpfs'. + type: list + elements: str + required: false + executable: + description: + - Path to C(podman) executable if it is not in the C($PATH) on the + machine running C(podman) + default: 'podman' + type: str + debug: + description: + - Return additional information which can be helpful for investigations. + type: bool + default: False + +requirements: + - "podman" + +''' + +RETURN = ''' +volume: + description: Volume inspection results if exists. + returned: always + type: dict + sample: + CreatedAt: '2020-06-05T16:38:55.277628769+03:00' + Driver: local + Labels: + key.com: value + key.org: value2 + Mountpoint: /home/user/.local/share/containers/storage/volumes/test/_data + Name: test + Options: {} + Scope: local + +''' + +EXAMPLES = ''' +# What modules does for example +- podman_volume: + state: present + name: volume1 + label: + key: value + key2: value2 + options: + - "device=/dev/loop1" + - "type=ext4" +''' +# noqa: F402 +import json # noqa: F402 +from distutils.version import LooseVersion # noqa: F402 + +from ansible.module_utils.basic import AnsibleModule # noqa: F402 +from ansible.module_utils._text import to_bytes, to_native # noqa: F402 + +from ansible_collections.containers.podman.plugins.module_utils.podman.common import lower_keys + + +class PodmanVolumeModuleParams: + """Creates list of arguments for podman CLI command. + + Arguments: + action {str} -- action type from 'create', 'delete' + params {dict} -- dictionary of module parameters + + """ + + def __init__(self, action, params, podman_version, module): + self.params = params + self.action = action + self.podman_version = podman_version + self.module = module + + def construct_command_from_params(self): + """Create a podman command from given module parameters. + + Returns: + list -- list of byte strings for Popen command + """ + if self.action in ['delete']: + return self._simple_action() + if self.action in ['create']: + return self._create_action() + + def _simple_action(self): + if self.action == 'delete': + cmd = ['rm', '-f', self.params['name']] + return [to_bytes(i, errors='surrogate_or_strict') for i in cmd] + + def _create_action(self): + cmd = [self.action, self.params['name']] + all_param_methods = [func for func in dir(self) + if callable(getattr(self, func)) + and func.startswith("addparam")] + params_set = (i for i in self.params if self.params[i] is not None) + for param in params_set: + func_name = "_".join(["addparam", param]) + if func_name in all_param_methods: + cmd = getattr(self, func_name)(cmd) + return [to_bytes(i, errors='surrogate_or_strict') for i in cmd] + + def check_version(self, param, minv=None, maxv=None): + if minv and LooseVersion(minv) > LooseVersion( + self.podman_version): + self.module.fail_json(msg="Parameter %s is supported from podman " + "version %s only! Current version is %s" % ( + param, minv, self.podman_version)) + if maxv and LooseVersion(maxv) < LooseVersion( + self.podman_version): + self.module.fail_json(msg="Parameter %s is supported till podman " + "version %s only! Current version is %s" % ( + param, minv, self.podman_version)) + + def addparam_label(self, c): + for label in self.params['label'].items(): + c += ['--label', b'='.join( + [to_bytes(l, errors='surrogate_or_strict') for l in label])] + return c + + def addparam_driver(self, c): + return c + ['--driver', self.params['driver']] + + def addparam_options(self, c): + for opt in self.params['options']: + c += ['--opt', opt] + return c + + +class PodmanVolumeDefaults: + def __init__(self, module, podman_version): + self.module = module + self.version = podman_version + self.defaults = { + 'driver': 'local', + 'label': {}, + 'options': {} + } + + def default_dict(self): + # make here any changes to self.defaults related to podman version + return self.defaults + + +class PodmanVolumeDiff: + def __init__(self, module, info, podman_version): + self.module = module + self.version = podman_version + self.default_dict = None + self.info = lower_keys(info) + self.params = self.defaultize() + self.diff = {'before': {}, 'after': {}} + self.non_idempotent = {} + + def defaultize(self): + params_with_defaults = {} + self.default_dict = PodmanVolumeDefaults( + self.module, self.version).default_dict() + for p in self.module.params: + if self.module.params[p] is None and p in self.default_dict: + params_with_defaults[p] = self.default_dict[p] + else: + params_with_defaults[p] = self.module.params[p] + return params_with_defaults + + def _diff_update_and_compare(self, param_name, before, after): + if before != after: + self.diff['before'].update({param_name: before}) + self.diff['after'].update({param_name: after}) + return True + return False + + def diffparam_label(self): + before = self.info['labels'] if 'labels' in self.info else {} + after = self.params['label'] + return self._diff_update_and_compare('label', before, after) + + def diffparam_driver(self): + before = self.info['driver'] + after = self.params['driver'] + return self._diff_update_and_compare('driver', before, after) + + def diffparam_options(self): + before = self.info['options'] if 'options' in self.info else {} + before = ["=".join((k, v)) for k, v in before.items()] + after = self.params['options'] + # Gor UID, GID + ids = [] + if self.info['uid']: + before += ['uid=%s' % str(self.info['uid'])] + if self.info['gid']: + before += ['gid=%s' % str(self.info['gid'])] + if self.params['options']: + for opt in self.params['options']: + if 'uid=' in opt or 'gid=' in opt: + ids += opt.split("o=")[1].split(",") + after = [i for i in after if 'gid' not in i and 'uid' not in i] + after += ids + before, after = sorted(list(set(before))), sorted(list(set(after))) + return self._diff_update_and_compare('options', before, after) + + def is_different(self): + diff_func_list = [func for func in dir(self) + if callable(getattr(self, func)) and func.startswith( + "diffparam")] + fail_fast = not bool(self.module._diff) + different = False + for func_name in diff_func_list: + dff_func = getattr(self, func_name) + if dff_func(): + if fail_fast: + return True + else: + different = True + # Check non idempotent parameters + for p in self.non_idempotent: + if self.module.params[p] is not None and self.module.params[p] not in [{}, [], '']: + different = True + return different + + +class PodmanVolume: + """Perform volume tasks. + + Manages podman volume, inspects it and checks its current state + """ + + def __init__(self, module, name): + """Initialize PodmanVolume class. + + Arguments: + module {obj} -- ansible module object + name {str} -- name of volume + """ + + super(PodmanVolume, self).__init__() + self.module = module + self.name = name + self.stdout, self.stderr = '', '' + self.info = self.get_info() + self.version = self._get_podman_version() + self.diff = {} + self.actions = [] + + @property + def exists(self): + """Check if volume exists.""" + return bool(self.info != {}) + + @property + def different(self): + """Check if volume is different.""" + diffcheck = PodmanVolumeDiff( + self.module, + self.info, + self.version) + is_different = diffcheck.is_different() + diffs = diffcheck.diff + if self.module._diff and is_different and diffs['before'] and diffs['after']: + self.diff['before'] = "\n".join( + ["%s - %s" % (k, v) for k, v in sorted( + diffs['before'].items())]) + "\n" + self.diff['after'] = "\n".join( + ["%s - %s" % (k, v) for k, v in sorted( + diffs['after'].items())]) + "\n" + return is_different + + def get_info(self): + """Inspect volume and gather info about it.""" + # pylint: disable=unused-variable + rc, out, err = self.module.run_command( + [self.module.params['executable'], b'volume', b'inspect', self.name]) + return json.loads(out)[0] if rc == 0 else {} + + def _get_podman_version(self): + # pylint: disable=unused-variable + rc, out, err = self.module.run_command( + [self.module.params['executable'], b'--version']) + if rc != 0 or not out or "version" not in out: + self.module.fail_json(msg="%s run failed!" % + self.module.params['executable']) + return out.split("version")[1].strip() + + def _perform_action(self, action): + """Perform action with volume. + + Arguments: + action {str} -- action to perform - create, stop, delete + """ + b_command = PodmanVolumeModuleParams(action, + self.module.params, + self.version, + self.module, + ).construct_command_from_params() + full_cmd = " ".join([self.module.params['executable'], 'volume'] + + [to_native(i) for i in b_command]) + self.module.log("PODMAN-VOLUME-DEBUG: %s" % full_cmd) + self.actions.append(full_cmd) + if not self.module.check_mode: + rc, out, err = self.module.run_command( + [self.module.params['executable'], b'volume'] + b_command, + expand_user_and_vars=False) + self.stdout = out + self.stderr = err + if rc != 0: + self.module.fail_json( + msg="Can't %s volume %s" % (action, self.name), + stdout=out, stderr=err) + + def delete(self): + """Delete the volume.""" + self._perform_action('delete') + + def create(self): + """Create the volume.""" + self._perform_action('create') + + def recreate(self): + """Recreate the volume.""" + self.delete() + self.create() + + +class PodmanVolumeManager: + """Module manager class. + + Defines according to parameters what actions should be applied to volume + """ + + def __init__(self, module): + """Initialize PodmanManager class. + + Arguments: + module {obj} -- ansible module object + """ + + super(PodmanVolumeManager, self).__init__() + + self.module = module + self.results = { + 'changed': False, + 'actions': [], + 'volume': {}, + } + self.name = self.module.params['name'] + self.executable = \ + self.module.get_bin_path(self.module.params['executable'], + required=True) + self.state = self.module.params['state'] + self.recreate = self.module.params['recreate'] + self.volume = PodmanVolume(self.module, self.name) + + def update_volume_result(self, changed=True): + """Inspect the current volume, update results with last info, exit. + + Keyword Arguments: + changed {bool} -- whether any action was performed + (default: {True}) + """ + facts = self.volume.get_info() if changed else self.volume.info + out, err = self.volume.stdout, self.volume.stderr + self.results.update({'changed': changed, 'volume': facts, + 'podman_actions': self.volume.actions}, + stdout=out, stderr=err) + if self.volume.diff: + self.results.update({'diff': self.volume.diff}) + if self.module.params['debug']: + self.results.update({'podman_version': self.volume.version}) + self.module.exit_json(**self.results) + + def execute(self): + """Execute the desired action according to map of actions & states.""" + states_map = { + 'present': self.make_present, + 'absent': self.make_absent, + } + process_action = states_map[self.state] + process_action() + self.module.fail_json(msg="Unexpected logic error happened, " + "please contact maintainers ASAP!") + + def make_present(self): + """Run actions if desired state is 'started'.""" + if not self.volume.exists: + self.volume.create() + self.results['actions'].append('created %s' % self.volume.name) + self.update_volume_result() + elif self.recreate or self.volume.different: + self.volume.recreate() + self.results['actions'].append('recreated %s' % + self.volume.name) + self.update_volume_result() + else: + self.update_volume_result(changed=False) + + def make_absent(self): + """Run actions if desired state is 'absent'.""" + if not self.volume.exists: + self.results.update({'changed': False}) + elif self.volume.exists: + self.volume.delete() + self.results['actions'].append('deleted %s' % self.volume.name) + self.results.update({'changed': True}) + self.results.update({'volume': {}, + 'podman_actions': self.volume.actions}) + self.module.exit_json(**self.results) + + +def main(): + module = AnsibleModule( + argument_spec=dict( + state=dict(type='str', default="present", + choices=['present', 'absent']), + name=dict(type='str', required=True), + label=dict(type='dict', required=False), + driver=dict(type='str', required=False), + options=dict(type='list', elements='str', required=False), + recreate=dict(type='bool', default=False), + executable=dict(type='str', required=False, default='podman'), + debug=dict(type='bool', default=False), + )) + + PodmanVolumeManager(module).execute() + + +if __name__ == '__main__': + main() diff --git a/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_volume_info.py b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_volume_info.py new file mode 100644 index 00000000..97b43b3c --- /dev/null +++ b/collections-debian-merged/ansible_collections/containers/podman/plugins/modules/podman_volume_info.py @@ -0,0 +1,100 @@ +#!/usr/bin/python +# Copyright (c) 2020 Red Hat +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +from __future__ import absolute_import, division, print_function +__metaclass__ = type + + +DOCUMENTATION = r''' +module: podman_volume_info +author: + - "Sagi Shnaidman (@sshnaidm)" +short_description: Gather info about podman volumes +notes: [] +description: + - Gather info about podman volumes with podman inspect command. +requirements: + - "Podman installed on host" +options: + name: + description: + - Name of the volume + type: str + executable: + description: + - Path to C(podman) executable if it is not in the C($PATH) on the + machine running C(podman) + default: 'podman' + type: str +''' + +EXAMPLES = r""" +- name: Gather info about all present volumes + podman_volume_info: + +- name: Gather info about specific volume + podman_volume_info: + name: specific_volume +""" + +RETURN = r""" +volumes: + description: Facts from all or specified volumes + returned: always + type: list + sample: [ + { + "name": "testvolume", + "labels": {}, + "mountPoint": "/home/ansible/.local/share/testvolume/_data", + "driver": "local", + "options": {}, + "scope": "local" + } + ] +""" + +import json +from ansible.module_utils.basic import AnsibleModule + + +def get_volume_info(module, executable, name): + command = [executable, 'volume', 'inspect'] + if name: + command.append(name) + else: + command.append("--all") + rc, out, err = module.run_command(command) + if rc != 0 or 'no such volume' in err: + module.fail_json(msg="Unable to gather info for %s: %s" % (name or 'all volumes', err)) + if not out or json.loads(out) is None: + return [], out, err + return json.loads(out), out, err + + +def main(): + module = AnsibleModule( + argument_spec=dict( + executable=dict(type='str', default='podman'), + name=dict(type='str') + ), + supports_check_mode=True, + ) + + name = module.params['name'] + executable = module.get_bin_path(module.params['executable'], required=True) + + inspect_results, out, err = get_volume_info(module, executable, name) + + results = { + "changed": False, + "volumes": inspect_results, + "stderr": err + } + + module.exit_json(**results) + + +if __name__ == '__main__': + main() |