#!/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 (@podman) - 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 specified containers returned: always type: list elements: dict sample: [ { "Id": "d38a8fcd61ab7e0754355e8fb3acc201e07770f3d1fd8fed36556941ac458ce", "Created": "2024-08-14T00:04:33.127266655+03:00", "Path": "/entrypoint.sh", "Args": [ "/entrypoint.sh" ], "State": { "OciVersion": "1.1.0+dev", "Status": "running", "Running": true, "Paused": false, "Restarting": false, "OOMKilled": false, "Dead": false, "Pid": 2434164, "ConmonPid": 2434162, "ExitCode": 0, "Error": "", "StartedAt": "2024-08-14T00:04:33.237286439+03:00", "FinishedAt": "0001-01-01T00:00:00Z", "Health": { "Status": "", "FailingStreak": 0, "Log": null }, "CgroupPath": "/user.slice/user-1000.slice/user@1000.service/user.slice/libpod-d38a....scope", "CheckpointedAt": "0001-01-01T00:00:00Z", "RestoredAt": "0001-01-01T00:00:00Z" }, "Image": "fe2ba3a8ede60e5938e666b483c3a812ba902dac2303341930fbadc0482592b7", "ImageDigest": "sha256:1222865ed7489298ee28414ddedb63a0c6405938c3a38adf21c8656d7f532271", "ImageName": "registry/org/image:latest", "Rootfs": "", "Pod": "", "ResolvConfPath": "/run/user/1000/containers/overlay-containers/d38a.../userdata/resolv.conf", "HostnamePath": "/run/user/1000/containers/overlay-containers/d38a.../userdata/hostname", "HostsPath": "/run/user/1000/containers/overlay-containers/d38a.../userdata/hosts", "StaticDir": "/home/podman/.local/share/containers/storage/overlay-containers/d38a.../userdata", "OCIConfigPath": "/home/podman/.local/share/containers/....json", "OCIRuntime": "crun", "ConmonPidFile": "/run/user/1000/containers/overlay-containers/d38a.../userdata/conmon.pid", "PidFile": "/run/user/1000/containers/overlay-containers/d38a.../userdata/pidfile", "Name": "costapp", "RestartCount": 0, "Driver": "overlay", "MountLabel": "system_u:object_r:container_file_t:s0:c493,c986", "ProcessLabel": "system_u:system_r:container_t:s0:c493,c986", "AppArmorProfile": "", "EffectiveCaps": [ "CAP_CHOWN", "CAP_DAC_OVERRIDE", "CAP_FOWNER", "CAP_FSETID", "CAP_KILL", "CAP_NET_BIND_SERVICE", "CAP_SETFCAP", "CAP_SETGID", "CAP_SETPCAP", "CAP_SETUID", "CAP_SYS_CHROOT" ], "BoundingCaps": [ "CAP_CHOWN", "CAP_DAC_OVERRIDE", "CAP_FOWNER", "CAP_FSETID", "CAP_KILL", "CAP_NET_BIND_SERVICE", "CAP_SETFCAP", "CAP_SETGID", "CAP_SETPCAP", "CAP_SETUID", "CAP_SYS_CHROOT" ], "ExecIDs": [], "GraphDriver": { "Name": "overlay", "Data": { "LowerDir": "/home/podman/.local/share/containers/storage/overlay/29e2.../diff:...", "MergedDir": "/home/podman/.local/share/containers/storage/overlay/865909.../merged", "UpperDir": "/home/podman/.local/share/containers/storage/overlay/865909.../diff", "WorkDir": "/home/podman/.local/share/containers/storage/overlay/865909.../work" } }, "Mounts": [], "Dependencies": [], "NetworkSettings": { "EndpointID": "", "Gateway": "", "IPAddress": "", "IPPrefixLen": 0, "IPv6Gateway": "", "GlobalIPv6Address": "", "GlobalIPv6PrefixLen": 0, "MacAddress": "", "Bridge": "", "SandboxID": "", "HairpinMode": false, "LinkLocalIPv6Address": "", "LinkLocalIPv6PrefixLen": 0, "Ports": { "80/tcp": [ { "HostIp": "", "HostPort": "8888" } ] }, "SandboxKey": "/run/user/1000/netns/netns-2343321-795a-8289-14c0-77ee2556ebf1" }, "Namespace": "", "IsInfra": false, "IsService": false, "KubeExitCodePropagation": "invalid", "lockNumber": 1417, "Config": { "Hostname": "444a8274863a", "Domainname": "", "User": "", "AttachStdin": false, "AttachStdout": false, "AttachStderr": false, "Tty": false, "OpenStdin": false, "StdinOnce": false, "Env": [ "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "container=podman", "HOME=/root", "HOSTNAME=444a8274863a" ], "Cmd": null, "Image": "registry/org/image:latest", "Volumes": null, "WorkingDir": "/", "Entrypoint": "/entrypoint.sh", "OnBuild": null, "Labels": { "io.buildah.version": "1.31.2" }, "Annotations": { "io.container.manager": "libpod", "org.opencontainers.image.stopSignal": "15" }, "StopSignal": 15, "HealthcheckOnFailureAction": "none", "CreateCommand": [ "podman", "run", "-d", "--name", "test", "-p", "8888:80", "registry/org/image:latest" ], "Umask": "0022", "Timeout": 0, "StopTimeout": 10, "Passwd": true, "sdNotifyMode": "container" }, "HostConfig": { "Binds": [], "CgroupManager": "systemd", "CgroupMode": "private", "ContainerIDFile": "", "LogConfig": { "Type": "journald", "Config": null, "Path": "", "Tag": "", "Size": "0B" }, "NetworkMode": "slirp4netns", "PortBindings": { "80/tcp": [ { "HostIp": "", "HostPort": "8888" } ] }, "RestartPolicy": { "Name": "", "MaximumRetryCount": 0 }, "AutoRemove": false, "VolumeDriver": "", "VolumesFrom": null, "CapAdd": [], "CapDrop": [], "Dns": [], "DnsOptions": [], "DnsSearch": [], "ExtraHosts": [], "GroupAdd": [], "IpcMode": "shareable", "Cgroup": "", "Cgroups": "default", "Links": null, "OomScoreAdj": 0, "PidMode": "private", "Privileged": false, "PublishAllPorts": false, "ReadonlyRootfs": false, "SecurityOpt": [], "Tmpfs": {}, "UTSMode": "private", "UsernsMode": "", "ShmSize": 65536000, "Runtime": "oci", "ConsoleSize": [ 0, 0 ], "Isolation": "", "CpuShares": 0, "Memory": 0, "NanoCpus": 0, "CgroupParent": "user.slice", "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": 0, "OomKillDisable": false, "PidsLimit": 2048, "Ulimits": [ { "Name": "RLIMIT_NOFILE", "Soft": 524288, "Hard": 524288 }, { "Name": "RLIMIT_NPROC", "Soft": 256018, "Hard": 256018 } ], "CpuCount": 0, "CpuPercent": 0, "IOMaximumIOps": 0, "IOMaximumBandwidth": 0, "CgroupConf": null, } } ] """ import json import time from ansible.module_utils.basic import AnsibleModule def get_containers_facts(module, executable, name): """Collect containers facts for all containers or for specified in 'name'. Arguments: module {AnsibleModule} -- instance of AnsibleModule executable {string} -- binary to execute when inspecting containers name {list} -- list of names or None in case of all containers Returns: list of containers info, stdout, stderr """ retry = 0 retry_limit = 4 if not name: all_names = [executable, 'container', 'ls', '-q', '-a'] rc, out, err = module.run_command(all_names) # This should not fail in regular circumstances, so retry again # https://github.com/containers/podman/issues/10225 while rc != 0 and retry <= retry_limit: module.log(msg="Unable to get list of containers: %s" % err) time.sleep(1) retry += 1 rc, out, err = module.run_command(all_names) if rc != 0: module.fail_json(msg="Unable to get list of containers during" " %s retries" % retry_limit) name = out.split() if not name: return [], out, err command = [executable, 'container', 'inspect'] command.extend(name) rc, out, err = module.run_command(command) if rc == 0: json_out = json.loads(out) if out else None if json_out is None: return [], out, err return json_out, out, err if rc != 0 and 'no such ' in err: if len(name) < 2: return [], out, err return cycle_over(module, executable, name) module.fail_json(msg="Unable to gather info for %s: %s" % (",".join(name), err)) def cycle_over(module, executable, name): """Inspect each container in a cycle in case some of them don't exist. Arguments: module {AnsibleModule} -- instance of AnsibleModule executable {string} -- binary to execute when inspecting containers name {list} -- list of containers names to inspect Returns: list of containers info, stdout as empty, stderr """ inspection = [] stderrs = [] for container in name: command = [executable, 'container', 'inspect', container] rc, out, err = module.run_command(command) if rc != 0 and 'no such ' not in err: module.fail_json(msg="Unable to gather info for %s: %s" % (container, err)) if rc == 0 and out: json_out = json.loads(out) if json_out: inspection += json_out stderrs.append(err) return inspection, "", "\n".join(stderrs) def main(): module = AnsibleModule( argument_spec={ 'executable': {'type': 'str', 'default': 'podman'}, 'name': {'type': 'list', 'elements': 'str'}, }, supports_check_mode=True, ) name = module.params['name'] executable = module.get_bin_path(module.params['executable'], required=True) # pylint: disable=unused-variable inspect_results, out, err = get_containers_facts(module, executable, name) results = { "changed": False, "containers": inspect_results, "stderr": err } module.exit_json(**results) if __name__ == '__main__': main()