TripleO Ansible project repository. Contains playbooks for use with TripleO OpenStack deployments.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

2157 lines
74 KiB

  1. #!/usr/bin/python
  2. # Copyright (c) 2019 OpenStack Foundation
  3. # All Rights Reserved.
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License"); you may
  6. # not use this file except in compliance with the License. You may obtain
  7. # a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  13. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  14. # License for the specific language governing permissions and limitations
  15. # under the License.
  16. # flake8: noqa: E501
  17. from __future__ import absolute_import, division, print_function
  18. __metaclass__ = type
  19. import json
  20. from distutils.version import LooseVersion
  21. import yaml
  22. from ansible.module_utils.basic import AnsibleModule
  23. from ansible.module_utils._text import to_bytes, to_native
  24. DOCUMENTATION = """
  25. module: podman_container
  26. author:
  27. - "Sagi Shnaidman (@sshnaidm)"
  28. version_added: '1.0.0'
  29. short_description: Manage podman containers
  30. notes: []
  31. description:
  32. - Start, stop, restart and manage Podman containers
  33. requirements:
  34. - "Podman installed on host"
  35. options:
  36. name:
  37. description:
  38. - Name of the container
  39. required: True
  40. type: str
  41. executable:
  42. description:
  43. - Path to C(podman) executable if it is not in the C($PATH) on the
  44. machine running C(podman)
  45. default: 'podman'
  46. type: str
  47. state:
  48. description:
  49. - I(absent) - A container matching the specified name will be stopped and
  50. removed.
  51. - I(present) - Asserts the existence of a container matching the name and
  52. any provided configuration parameters. If no container matches the
  53. name, a container will be created. If a container matches the name but
  54. the provided configuration does not match, the container will be
  55. updated, if it can be. If it cannot be updated, it will be removed and
  56. re-created with the requested config. Image version will be taken into
  57. account when comparing configuration. Use the recreate option to force
  58. the re-creation of the matching container.
  59. - I(started) - Asserts there is a running container matching the name and
  60. any provided configuration. If no container matches the name, a
  61. container will be created and started. Use recreate to always re-create
  62. a matching container, even if it is running. Use force_restart to force
  63. a matching container to be stopped and restarted.
  64. - I(stopped) - Asserts that the container is first I(present), and then
  65. if the container is running moves it to a stopped state.
  66. type: str
  67. default: started
  68. choices:
  69. - absent
  70. - present
  71. - stopped
  72. - started
  73. image:
  74. description:
  75. - Repository path (or image name) and tag used to create the container.
  76. If an image is not found, the image will be pulled from the registry.
  77. If no tag is included, C(latest) will be used.
  78. - Can also be an image ID. If this is the case, the image is assumed to
  79. be available locally.
  80. type: str
  81. annotation:
  82. description:
  83. - Add an annotation to the container. The format is key value, multiple
  84. times.
  85. type: dict
  86. authfile:
  87. description:
  88. - Path of the authentication file. Default is
  89. ``${XDG_RUNTIME_DIR}/containers/auth.json``
  90. (Not available for remote commands) You can also override the default
  91. path of the authentication file by setting the ``REGISTRY_AUTH_FILE``
  92. environment variable. ``export REGISTRY_AUTH_FILE=path``
  93. type: path
  94. blkio_weight:
  95. description:
  96. - Block IO weight (relative weight) accepts a weight value between 10 and
  97. 1000
  98. type: int
  99. blkio_weight_device:
  100. description:
  101. - Block IO weight (relative device weight, format DEVICE_NAME[:]WEIGHT).
  102. type: dict
  103. cap_add:
  104. description:
  105. - List of capabilities to add to the container.
  106. type: list
  107. elements: str
  108. cap_drop:
  109. description:
  110. - List of capabilities to drop from the container.
  111. type: list
  112. elements: str
  113. cgroup_parent:
  114. description:
  115. - Path to cgroups under which the cgroup for the container will be
  116. created.
  117. If the path is not absolute, the path is considered to be relative to
  118. the cgroups path of the init process. Cgroups will be created if they
  119. do not already exist.
  120. type: path
  121. cgroupns:
  122. description:
  123. - Path to cgroups under which the cgroup for the container will be
  124. created.
  125. type: str
  126. cgroups:
  127. description:
  128. - Determines whether the container will create CGroups.
  129. Valid values are enabled and disabled, which the default being enabled.
  130. The disabled option will force the container to not create CGroups,
  131. and thus conflicts with CGroup options cgroupns and cgroup-parent.
  132. type: str
  133. choices:
  134. - default
  135. - disabled
  136. cidfile:
  137. description:
  138. - Write the container ID to the file
  139. type: path
  140. cmd_args:
  141. description:
  142. - Any additional command options you want to pass to podman command,
  143. cmd_args - ['--other-param', 'value']
  144. Be aware module doesn't support idempotency if this is set.
  145. type: list
  146. elements: str
  147. conmon_pidfile:
  148. description:
  149. - Write the pid of the conmon process to a file.
  150. conmon runs in a separate process than Podman,
  151. so this is necessary when using systemd to restart Podman containers.
  152. type: path
  153. command:
  154. description:
  155. - Override command of container. Can be a string or a list.
  156. type: raw
  157. cpu_period:
  158. description:
  159. - Limit the CPU real-time period in microseconds
  160. type: int
  161. cpu_rt_period:
  162. description:
  163. - Limit the CPU real-time period in microseconds.
  164. Limit the container's Real Time CPU usage. This flag tell the kernel to
  165. restrict the container's Real Time CPU usage to the period you specify.
  166. type: int
  167. cpu_rt_runtime:
  168. description:
  169. - Limit the CPU real-time runtime in microseconds.
  170. This flag tells the kernel to limit the amount of time in a given CPU
  171. period Real Time tasks may consume.
  172. type: int
  173. cpu_shares:
  174. description:
  175. - CPU shares (relative weight)
  176. type: int
  177. cpus:
  178. description:
  179. - Number of CPUs. The default is 0.0 which means no limit.
  180. type: str
  181. cpuset_cpus:
  182. description:
  183. - CPUs in which to allow execution (0-3, 0,1)
  184. type: str
  185. cpuset_mems:
  186. description:
  187. - Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only
  188. effective on NUMA systems.
  189. type: str
  190. detach:
  191. description:
  192. - Run container in detach mode
  193. type: bool
  194. default: True
  195. debug:
  196. description:
  197. - Return additional information which can be helpful for investigations.
  198. type: bool
  199. default: False
  200. detach_keys:
  201. description:
  202. - Override the key sequence for detaching a container. Format is a single
  203. character or ctrl-value
  204. type: str
  205. device:
  206. description:
  207. - Add a host device to the container.
  208. The format is <device-on-host>[:<device-on-container>][:<permissions>]
  209. (e.g. device /dev/sdc:/dev/xvdc:rwm)
  210. type: list
  211. elements: str
  212. device_read_bps:
  213. description:
  214. - Limit read rate (bytes per second) from a device
  215. (e.g. device-read-bps /dev/sda:1mb)
  216. type: list
  217. device_read_iops:
  218. description:
  219. - Limit read rate (IO per second) from a device
  220. (e.g. device-read-iops /dev/sda:1000)
  221. type: list
  222. device_write_bps:
  223. description:
  224. - Limit write rate (bytes per second) to a device
  225. (e.g. device-write-bps /dev/sda:1mb)
  226. type: list
  227. device_write_iops:
  228. description:
  229. - Limit write rate (IO per second) to a device
  230. (e.g. device-write-iops /dev/sda:1000)
  231. type: list
  232. dns:
  233. description:
  234. - Set custom DNS servers
  235. type: list
  236. elements: str
  237. dns_option:
  238. description:
  239. - Set custom DNS options
  240. type: str
  241. dns_search:
  242. description:
  243. - Set custom DNS search domains (Use dns_search with '' if you don't wish
  244. to set the search domain)
  245. type: str
  246. entrypoint:
  247. description:
  248. - Overwrite the default ENTRYPOINT of the image
  249. type: str
  250. env:
  251. description:
  252. - Set environment variables.
  253. This option allows you to specify arbitrary environment variables that
  254. are available for the process that will be launched inside of the
  255. container.
  256. type: dict
  257. env_file:
  258. description:
  259. - Read in a line delimited file of environment variables
  260. type: path
  261. env_host:
  262. description:
  263. - Use all current host environment variables in container.
  264. Defaults to false.
  265. type: bool
  266. etc_hosts:
  267. description:
  268. - Dict of host-to-IP mappings, where each host name is a key in the
  269. dictionary. Each host name will be added to the container's
  270. ``/etc/hosts`` file.
  271. type: dict
  272. aliases:
  273. - add_hosts
  274. expose:
  275. description:
  276. - Expose a port, or a range of ports (e.g. expose "3300-3310") to set up
  277. port redirection on the host system.
  278. type: list
  279. elements: str
  280. aliases:
  281. - exposed
  282. - exposed_ports
  283. force_restart:
  284. description:
  285. - Force restart of container.
  286. type: bool
  287. default: False
  288. aliases:
  289. - restart
  290. gidmap:
  291. description:
  292. - Run the container in a new user namespace using the supplied mapping.
  293. type: str
  294. group_add:
  295. description:
  296. - Add additional groups to run as
  297. type: list
  298. healthcheck:
  299. description:
  300. - Set or alter a healthcheck command for a container.
  301. type: str
  302. healthcheck_interval:
  303. description:
  304. - Set an interval for the healthchecks
  305. (a value of disable results in no automatic timer setup)
  306. (default "30s")
  307. type: str
  308. healthcheck_retries:
  309. description:
  310. - The number of retries allowed before a healthcheck is considered to be
  311. unhealthy. The default value is 3.
  312. type: int
  313. healthcheck_start_period:
  314. description:
  315. - The initialization time needed for a container to bootstrap.
  316. The value can be expressed in time format like 2m3s. The default value
  317. is 0s
  318. type: str
  319. healthcheck_timeout:
  320. description:
  321. - The maximum time allowed to complete the healthcheck before an interval
  322. is considered failed. Like start-period, the value can be expressed in
  323. a time format such as 1m22s. The default value is 30s
  324. type: str
  325. hostname:
  326. description:
  327. - Container host name. Sets the container host name that is available
  328. inside the container.
  329. type: str
  330. http_proxy:
  331. description:
  332. - By default proxy environment variables are passed into the container if
  333. set for the podman process. This can be disabled by setting the
  334. http_proxy option to false. The environment variables passed in
  335. include http_proxy, https_proxy, ftp_proxy, no_proxy, and also the
  336. upper case versions of those.
  337. Defaults to true
  338. type: bool
  339. image_volume:
  340. description:
  341. - Tells podman how to handle the builtin image volumes.
  342. The options are bind, tmpfs, or ignore (default bind)
  343. type: str
  344. choices:
  345. - 'bind'
  346. - 'tmpfs'
  347. - 'ignore'
  348. image_strict:
  349. description:
  350. - Whether to compare images in idempotency by taking into account a full
  351. name with registry and namespaces.
  352. type: bool
  353. default: False
  354. init:
  355. description:
  356. - Run an init inside the container that forwards signals and reaps
  357. processes.
  358. type: str
  359. init_path:
  360. description:
  361. - Path to the container-init binary.
  362. type: str
  363. interactive:
  364. description:
  365. - Keep STDIN open even if not attached. The default is false.
  366. When set to true, keep stdin open even if not attached.
  367. The default is false.
  368. type: bool
  369. ip:
  370. description:
  371. - Specify a static IP address for the container, for example
  372. '10.88.64.128'.
  373. Can only be used if no additional CNI networks to join were specified
  374. via 'network:', and if the container is not joining another container's
  375. network namespace via 'network container:<name|id>'.
  376. The address must be within the default CNI network's pool
  377. (default 10.88.0.0/16).
  378. type: str
  379. ipc:
  380. description:
  381. - Default is to create a private IPC namespace (POSIX SysV IPC) for the
  382. container
  383. type: str
  384. kernel_memory:
  385. description:
  386. - Kernel memory limit
  387. (format <number>[<unit>], where unit = b, k, m or g)
  388. Note - idempotency is supported for integers only.
  389. type: str
  390. label:
  391. description:
  392. - Add metadata to a container, pass dictionary of label names and values
  393. type: dict
  394. label_file:
  395. description:
  396. - Read in a line delimited file of labels
  397. type: str
  398. log_driver:
  399. description:
  400. - Logging driver. Used to set the log driver for the container.
  401. For example log_driver "k8s-file".
  402. type: str
  403. choices:
  404. - k8s-file
  405. - journald
  406. - json-file
  407. log_level:
  408. description:
  409. - Logging level for Podman. Log messages above specified level
  410. ("debug"|"info"|"warn"|"error"|"fatal"|"panic") (default "error")
  411. type: str
  412. choices:
  413. - debug
  414. - info
  415. - warn
  416. - error
  417. - fatal
  418. - panic
  419. log_opt:
  420. description:
  421. - Logging driver specific options. Used to set the path to the container
  422. log file. For example log_opt
  423. "path=/var/log/container/mycontainer.json"
  424. type: str
  425. aliases:
  426. - log_options
  427. memory:
  428. description:
  429. - Memory limit (format 10k, where unit = b, k, m or g)
  430. Note - idempotency is supported for integers only.
  431. type: str
  432. memory_reservation:
  433. description:
  434. - Memory soft limit (format 100m, where unit = b, k, m or g)
  435. Note - idempotency is supported for integers only.
  436. type: str
  437. memory_swap:
  438. description:
  439. - A limit value equal to memory plus swap. Must be used with the -m
  440. (--memory) flag.
  441. The swap LIMIT should always be larger than -m (--memory) value.
  442. By default, the swap LIMIT will be set to double the value of --memory
  443. Note - idempotency is supported for integers only.
  444. type: str
  445. memory_swappiness:
  446. description:
  447. - Tune a container's memory swappiness behavior. Accepts an integer
  448. between 0 and 100.
  449. type: int
  450. mount:
  451. description:
  452. - Attach a filesystem mount to the container. bind or tmpfs
  453. For example mount
  454. "type=bind,source=/path/on/host,destination=/path/in/container"
  455. type: str
  456. network:
  457. description:
  458. - Set the Network mode for the container
  459. * bridge create a network stack on the default bridge
  460. * none no networking
  461. * container:<name|id> reuse another container's network stack
  462. * host use the podman host network stack.
  463. * <network-name>|<network-id> connect to a user-defined network
  464. * ns:<path> path to a network namespace to join
  465. * slirp4netns use slirp4netns to create a user network stack.
  466. This is the default for rootless containers
  467. type: list
  468. elements: str
  469. aliases:
  470. - net
  471. no_hosts:
  472. description:
  473. - Do not create /etc/hosts for the container
  474. Default is false.
  475. type: bool
  476. oom_kill_disable:
  477. description:
  478. - Whether to disable OOM Killer for the container or not.
  479. Default is false.
  480. type: bool
  481. oom_score_adj:
  482. description:
  483. - Tune the host's OOM preferences for containers (accepts -1000 to 1000)
  484. type: int
  485. pid:
  486. description:
  487. - Set the PID mode for the container
  488. type: str
  489. pids_limit:
  490. description:
  491. - Tune the container's PIDs limit. Set -1 to have unlimited PIDs for the
  492. container.
  493. type: str
  494. pod:
  495. description:
  496. - Run container in an existing pod.
  497. If you want podman to make the pod for you, preference the pod name
  498. with "new:"
  499. type: str
  500. privileged:
  501. description:
  502. - Give extended privileges to this container. The default is false.
  503. type: bool
  504. publish:
  505. description:
  506. - Publish a container's port, or range of ports, to the host.
  507. Format - ip:hostPort:containerPort | ip::containerPort |
  508. hostPort:containerPort | containerPort
  509. type: list
  510. elements: str
  511. aliases:
  512. - ports
  513. - published
  514. - published_ports
  515. publish_all:
  516. description:
  517. - Publish all exposed ports to random ports on the host interfaces. The
  518. default is false.
  519. type: bool
  520. read_only:
  521. description:
  522. - Mount the container's root filesystem as read only. Default is false
  523. type: bool
  524. read_only_tmpfs:
  525. description:
  526. - If container is running in --read-only mode, then mount a read-write
  527. tmpfs on /run, /tmp, and /var/tmp. The default is true
  528. type: bool
  529. recreate:
  530. description:
  531. - Use with present and started states to force the re-creation of an
  532. existing container.
  533. type: bool
  534. default: False
  535. restart_policy:
  536. description:
  537. - Restart policy to follow when containers exit.
  538. Restart policy will not take effect if a container is stopped via the
  539. podman kill or podman stop commands. Valid values are
  540. * no - Do not restart containers on exit
  541. * on-failure[:max_retries] - Restart containers when they exit with a
  542. non-0 exit code, retrying indefinitely
  543. or until the optional max_retries count is hit
  544. * always - Restart containers when they exit, regardless of status,
  545. retrying indefinitely
  546. type: str
  547. rm:
  548. description:
  549. - Automatically remove the container when it exits. The default is false.
  550. type: bool
  551. aliases:
  552. - remove
  553. rootfs:
  554. description:
  555. - If true, the first argument refers to an exploded container on the file
  556. system. The default is false.
  557. type: bool
  558. default: False
  559. security_opt:
  560. description:
  561. - Security Options. For example security_opt "seccomp=unconfined"
  562. type: list
  563. elements: str
  564. shm_size:
  565. description:
  566. - Size of /dev/shm. The format is <number><unit>. number must be greater
  567. than 0.
  568. Unit is optional and can be b (bytes), k (kilobytes), m(megabytes), or
  569. g (gigabytes).
  570. If you omit the unit, the system uses bytes. If you omit the size
  571. entirely, the system uses 64m
  572. type: str
  573. sig_proxy:
  574. description:
  575. - Proxy signals sent to the podman run command to the container process.
  576. SIGCHLD, SIGSTOP, and SIGKILL are not proxied. The default is true.
  577. type: bool
  578. stop_signal:
  579. description:
  580. - Signal to stop a container. Default is SIGTERM.
  581. type: int
  582. stop_timeout:
  583. description:
  584. - Timeout (in seconds) to stop a container. Default is 10.
  585. type: int
  586. subgidname:
  587. description:
  588. - Run the container in a new user namespace using the map with 'name' in
  589. the /etc/subgid file.
  590. type: str
  591. subuidname:
  592. description:
  593. - Run the container in a new user namespace using the map with 'name' in
  594. the /etc/subuid file.
  595. type: str
  596. sysctl:
  597. description:
  598. - Configure namespaced kernel parameters at runtime
  599. type: dict
  600. systemd:
  601. description:
  602. - Run container in systemd mode. The default is true.
  603. type: bool
  604. tmpfs:
  605. description:
  606. - Create a tmpfs mount. For example tmpfs
  607. "/tmp" "rw,size=787448k,mode=1777"
  608. type: dict
  609. tty:
  610. description:
  611. - Allocate a pseudo-TTY. The default is false.
  612. type: bool
  613. uidmap:
  614. description:
  615. - Run the container in a new user namespace using the supplied mapping.
  616. type: list
  617. ulimit:
  618. description:
  619. - Ulimit options
  620. type: list
  621. user:
  622. description:
  623. - Sets the username or UID used and optionally the groupname or GID for
  624. the specified command.
  625. type: str
  626. userns:
  627. description:
  628. - Set the user namespace mode for the container.
  629. It defaults to the PODMAN_USERNS environment variable.
  630. An empty value means user namespaces are disabled.
  631. type: str
  632. uts:
  633. description:
  634. - Set the UTS mode for the container
  635. type: str
  636. volume:
  637. description:
  638. - Create a bind mount. If you specify, volume /HOST-DIR:/CONTAINER-DIR,
  639. podman bind mounts /HOST-DIR in the host to /CONTAINER-DIR in the
  640. podman container.
  641. type: list
  642. elements: str
  643. aliases:
  644. - volumes
  645. volumes_from:
  646. description:
  647. - Mount volumes from the specified container(s).
  648. type: list
  649. elements: str
  650. workdir:
  651. description:
  652. - Working directory inside the container.
  653. The default working directory for running binaries within a container
  654. is the root directory (/).
  655. type: str
  656. """
  657. EXAMPLES = """
  658. - name: Run container
  659. podman_container:
  660. name: container
  661. image: quay.io/bitnami/wildfly
  662. state: started
  663. - name: Create a data container
  664. podman_container:
  665. name: mydata
  666. image: busybox
  667. volume:
  668. - /tmp/data
  669. - name: Re-create a redis container
  670. podman_container:
  671. name: myredis
  672. image: redis
  673. command: redis-server --appendonly yes
  674. state: present
  675. recreate: yes
  676. expose:
  677. - 6379
  678. volumes_from:
  679. - mydata
  680. - name: Restart a container
  681. podman_container:
  682. name: myapplication
  683. image: redis
  684. state: started
  685. restart: yes
  686. etc_hosts:
  687. other: "127.0.0.1"
  688. restart_policy: "no"
  689. device: "/dev/sda:/dev/xvda:rwm"
  690. ports:
  691. - "8080:9000"
  692. - "127.0.0.1:8081:9001/udp"
  693. env:
  694. SECRET_KEY: "ssssh"
  695. BOOLEAN_KEY: "yes"
  696. - name: Container present
  697. podman_container:
  698. name: mycontainer
  699. state: present
  700. image: ubuntu:14.04
  701. command: "sleep 1d"
  702. - name: Stop a container
  703. podman_container:
  704. name: mycontainer
  705. state: stopped
  706. - name: Start 4 load-balanced containers
  707. podman_container:
  708. name: "container{{ item }}"
  709. recreate: yes
  710. image: someuser/anotherappimage
  711. command: sleep 1d
  712. with_sequence: count=4
  713. - name: remove container
  714. podman_container:
  715. name: ohno
  716. state: absent
  717. - name: Writing output
  718. podman_container:
  719. name: myservice
  720. image: busybox
  721. log_options: path=/var/log/container/mycontainer.json
  722. log_driver: k8s-file
  723. """
  724. RETURN = """
  725. container:
  726. description:
  727. - Facts representing the current state of the container. Matches the
  728. podman inspection output.
  729. - Note that facts are part of the registered vars since Ansible 2.8. For
  730. compatibility reasons, the facts
  731. are also accessible directly as C(podman_container). Note that the
  732. returned fact will be removed in Ansible 2.12.
  733. - Empty if C(state) is I(absent).
  734. returned: always
  735. type: dict
  736. sample: '{
  737. "AppArmorProfile": "",
  738. "Args": [
  739. "sh"
  740. ],
  741. "BoundingCaps": [
  742. "CAP_CHOWN",
  743. ...
  744. ],
  745. "Config": {
  746. "Annotations": {
  747. "io.kubernetes.cri-o.ContainerType": "sandbox",
  748. "io.kubernetes.cri-o.TTY": "false"
  749. },
  750. "AttachStderr": false,
  751. "AttachStdin": false,
  752. "AttachStdout": false,
  753. "Cmd": [
  754. "sh"
  755. ],
  756. "Domainname": "",
  757. "Entrypoint": "",
  758. "Env": [
  759. "PATH=/usr/sbin:/usr/bin:/sbin:/bin",
  760. "TERM=xterm",
  761. "HOSTNAME=",
  762. "container=podman"
  763. ],
  764. "Hostname": "",
  765. "Image": "docker.io/library/busybox:latest",
  766. "Labels": null,
  767. "OpenStdin": false,
  768. "StdinOnce": false,
  769. "StopSignal": 15,
  770. "Tty": false,
  771. "User": {
  772. "gid": 0,
  773. "uid": 0
  774. },
  775. "Volumes": null,
  776. "WorkingDir": "/"
  777. },
  778. "ConmonPidFile": "...",
  779. "Created": "2019-06-17T19:13:09.873858307+03:00",
  780. "Dependencies": [],
  781. "Driver": "overlay",
  782. "EffectiveCaps": [
  783. "CAP_CHOWN",
  784. ...
  785. ],
  786. "ExecIDs": [],
  787. "ExitCommand": [
  788. "/usr/bin/podman",
  789. "--root",
  790. ...
  791. ],
  792. "GraphDriver": {
  793. ...
  794. },
  795. "HostConfig": {
  796. ...
  797. },
  798. "HostnamePath": "...",
  799. "HostsPath": "...",
  800. "ID": "...",
  801. "Image": "...",
  802. "ImageName": "docker.io/library/busybox:latest",
  803. "IsInfra": false,
  804. "LogPath": "/tmp/container/mycontainer.json",
  805. "MountLabel": "system_u:object_r:container_file_t:s0:c282,c782",
  806. "Mounts": [
  807. ...
  808. ],
  809. "Name": "myservice",
  810. "Namespace": "",
  811. "NetworkSettings": {
  812. "Bridge": "",
  813. ...
  814. },
  815. "Path": "sh",
  816. "ProcessLabel": "system_u:system_r:container_t:s0:c282,c782",
  817. "ResolvConfPath": "...",
  818. "RestartCount": 0,
  819. "Rootfs": "",
  820. "State": {
  821. "Dead": false,
  822. "Error": "",
  823. "ExitCode": 0,
  824. "FinishedAt": "2019-06-17T19:13:10.157518963+03:00",
  825. "Healthcheck": {
  826. "FailingStreak": 0,
  827. "Log": null,
  828. "Status": ""
  829. },
  830. "OOMKilled": false,
  831. "OciVersion": "1.0.1-dev",
  832. "Paused": false,
  833. "Pid": 4083,
  834. "Restarting": false,
  835. "Running": false,
  836. "StartedAt": "2019-06-17T19:13:10.152479729+03:00",
  837. "Status": "exited"
  838. },
  839. "StaticDir": "..."
  840. ...
  841. }'
  842. """
  843. class PodmanModuleParams:
  844. """Creates list of arguments for podman CLI command.
  845. Arguments:
  846. action {str} -- action type from 'run', 'stop', 'create', 'delete',
  847. 'start'
  848. params {dict} -- dictionary of module parameters
  849. """
  850. def __init__(self, action, params, podman_version, module):
  851. self.params = params
  852. self.action = action
  853. self.podman_version = podman_version
  854. self.module = module
  855. def construct_command_from_params(self):
  856. """Create a podman command from given module parameters.
  857. Returns:
  858. list -- list of byte strings for Popen command
  859. """
  860. if self.action in ['start', 'stop', 'delete']:
  861. return self.start_stop_delete()
  862. if self.action in ['create', 'run']:
  863. cmd = [self.action, '--name', self.params['name']]
  864. all_param_methods = [func for func in dir(self)
  865. if callable(getattr(self, func))
  866. and func.startswith("addparam")]
  867. params_set = (i for i in self.params if self.params[i] is not None)
  868. for param in params_set:
  869. func_name = "_".join(["addparam", param])
  870. if func_name in all_param_methods:
  871. cmd = getattr(self, func_name)(cmd)
  872. cmd.append(self.params['image'])
  873. if self.params['command']:
  874. if isinstance(self.params['command'], list):
  875. cmd += self.params['command']
  876. else:
  877. cmd += self.params['command'].split()
  878. return [to_bytes(i, errors='surrogate_or_strict') for i in cmd]
  879. def start_stop_delete(self):
  880. if self.action in ['stop', 'start']:
  881. cmd = [self.action, self.params['name']]
  882. return [to_bytes(i, errors='surrogate_or_strict') for i in cmd]
  883. if self.action == 'delete':
  884. cmd = ['rm', '-f', self.params['name']]
  885. return [to_bytes(i, errors='surrogate_or_strict') for i in cmd]
  886. def check_version(self, param, minv=None, maxv=None):
  887. if minv and LooseVersion(minv) > LooseVersion(
  888. self.podman_version):
  889. self.module.fail_json(msg="Parameter %s is supported from podman "
  890. "version %s only! Current version is %s" % (
  891. param, minv, self.podman_version))
  892. if maxv and LooseVersion(maxv) < LooseVersion(
  893. self.podman_version):
  894. self.module.fail_json(msg="Parameter %s is supported till podman "
  895. "version %s only! Current version is %s" % (
  896. param, minv, self.podman_version))
  897. def addparam_annotation(self, c):
  898. for annotate in self.params['annotation'].items():
  899. c += ['--annotation', '='.join(annotate)]
  900. return c
  901. def addparam_authfile(self, c):
  902. return c + ['--authfile', self.params['authfile']]
  903. def addparam_blkio_weight(self, c):
  904. return c + ['--blkio-weight', self.params['blkio_weight']]
  905. def addparam_blkio_weight_device(self, c):
  906. for blkio in self.params['blkio_weight_device'].items():
  907. c += ['--blkio-weight-device', ':'.join(blkio)]
  908. return c
  909. def addparam_cap_add(self, c):
  910. for cap_add in self.params['cap_add']:
  911. c += ['--cap-add', cap_add]
  912. return c
  913. def addparam_cap_drop(self, c):
  914. for cap_drop in self.params['cap_drop']:
  915. c += ['--cap-drop', cap_drop]
  916. return c
  917. def addparam_cgroups(self, c):
  918. self.check_version('--cgroups', minv='1.6.0')
  919. return c + ['--cgroups=%s' % self.params['cgroups']]
  920. def addparam_cgroupns(self, c):
  921. self.check_version('--cgroupns', minv='1.6.2')
  922. return c + ['--cgroupns=%s' % self.params['cgroupns']]
  923. def addparam_cgroup_parent(self, c):
  924. return c + ['--cgroup-parent', self.params['cgroup_parent']]
  925. def addparam_cidfile(self, c):
  926. return c + ['--cidfile', self.params['cidfile']]
  927. def addparam_conmon_pidfile(self, c):
  928. return c + ['--conmon-pidfile', self.params['conmon_pidfile']]
  929. def addparam_cpu_period(self, c):
  930. return c + ['--cpu-period', self.params['cpu_period']]
  931. def addparam_cpu_rt_period(self, c):
  932. return c + ['--cpu-rt-period', self.params['cpu_rt_period']]
  933. def addparam_cpu_rt_runtime(self, c):
  934. return c + ['--cpu-rt-runtime', self.params['cpu_rt_runtime']]
  935. def addparam_cpu_shares(self, c):
  936. return c + ['--cpu-shares', self.params['cpu_shares']]
  937. def addparam_cpus(self, c):
  938. return c + ['--cpus', self.params['cpus']]
  939. def addparam_cpuset_cpus(self, c):
  940. return c + ['--cpuset-cpus', self.params['cpuset_cpus']]
  941. def addparam_cpuset_mems(self, c):
  942. return c + ['--cpuset-mems', self.params['cpuset_mems']]
  943. def addparam_detach(self, c):
  944. return c + ['--detach=%s' % self.params['detach']]
  945. def addparam_detach_keys(self, c):
  946. return c + ['--detach-keys', self.params['detach_keys']]
  947. def addparam_device(self, c):
  948. for dev in self.params['device']:
  949. c += ['--device', dev]
  950. return c
  951. def addparam_device_read_bps(self, c):
  952. for dev in self.params['device_read_bps']:
  953. c += ['--device-read-bps', dev]
  954. return c
  955. def addparam_device_read_iops(self, c):
  956. for dev in self.params['device_read_iops']:
  957. c += ['--device-read-iops', dev]
  958. return c
  959. def addparam_device_write_bps(self, c):
  960. for dev in self.params['device_write_bps']:
  961. c += ['--device-write-bps', dev]
  962. return c
  963. def addparam_device_write_iops(self, c):
  964. for dev in self.params['device_write_iops']:
  965. c += ['--device-write-iops', dev]
  966. return c
  967. def addparam_dns(self, c):
  968. return c + ['--dns', ','.join(self.params['dns'])]
  969. def addparam_dns_option(self, c):
  970. return c + ['--dns-option', self.params['dns_option']]
  971. def addparam_dns_search(self, c):
  972. return c + ['--dns-search', self.params['dns_search']]
  973. def addparam_entrypoint(self, c):
  974. return c + ['--entrypoint', self.params['entrypoint']]
  975. def addparam_env(self, c):
  976. for env_value in self.params['env'].items():
  977. c += ['--env',
  978. b"=".join([to_bytes(k, errors='surrogate_or_strict')
  979. for k in env_value])]
  980. return c
  981. def addparam_env_file(self, c):
  982. return c + ['--env-file', self.params['env_file']]
  983. def addparam_env_host(self, c):
  984. self.check_version('--env-host', minv='1.5.0')
  985. return c + ['--env-host=%s' % self.params['env_host']]
  986. def addparam_etc_hosts(self, c):
  987. for host_ip in self.params['etc_hosts'].items():
  988. c += ['--add-host', ':'.join(host_ip)]
  989. return c
  990. def addparam_expose(self, c):
  991. for exp in self.params['expose']:
  992. c += ['--expose', exp]
  993. return c
  994. def addparam_gidmap(self, c):
  995. return c + ['--gidmap', self.params['gidmap']]
  996. def addparam_group_add(self, c):
  997. for g in self.params['group_add']:
  998. c += ['--group-add', g]
  999. return c
  1000. def addparam_healthcheck(self, c):
  1001. return c + ['--healthcheck-command', self.params['healthcheck']]
  1002. def addparam_healthcheck_interval(self, c):
  1003. return c + ['--healthcheck-interval',
  1004. self.params['healthcheck_interval']]
  1005. def addparam_healthcheck_retries(self, c):
  1006. return c + ['--healthcheck-retries',
  1007. self.params['healthcheck_retries']]
  1008. def addparam_healthcheck_start_period(self, c):
  1009. return c + ['--healthcheck-start-period',
  1010. self.params['healthcheck_start_period']]
  1011. def addparam_healthcheck_timeout(self, c):
  1012. return c + ['--healthcheck-timeout',
  1013. self.params['healthcheck_timeout']]
  1014. def addparam_hostname(self, c):
  1015. return c + ['--hostname', self.params['hostname']]
  1016. def addparam_http_proxy(self, c):
  1017. return c + ['--http-proxy=%s' % self.params['http_proxy']]
  1018. def addparam_image_volume(self, c):
  1019. return c + ['--image-volume', self.params['image_volume']]
  1020. def addparam_init(self, c):
  1021. return c + ['--init', self.params['init']]
  1022. def addparam_init_path(self, c):
  1023. return c + ['--init-path', self.params['init_path']]
  1024. def addparam_interactive(self, c):
  1025. return c + ['--interactive=%s' % self.params['interactive']]
  1026. def addparam_ip(self, c):
  1027. return c + ['--ip', self.params['ip']]
  1028. def addparam_ipc(self, c):
  1029. return c + ['--ipc', self.params['ipc']]
  1030. def addparam_kernel_memory(self, c):
  1031. return c + ['--kernel-memory', self.params['kernel_memory']]
  1032. def addparam_label(self, c):
  1033. for label in self.params['label'].items():
  1034. c += ['--label', b'='.join([to_bytes(l, errors='surrogate_or_strict')
  1035. for l in label])]
  1036. return c
  1037. def addparam_label_file(self, c):
  1038. return c + ['--label-file', self.params['label_file']]
  1039. def addparam_log_driver(self, c):
  1040. return c + ['--log-driver', self.params['log_driver']]
  1041. def addparam_log_opt(self, c):
  1042. return c + ['--log-opt', self.params['log_opt']]
  1043. def addparam_log_level(self, c):
  1044. return c + ['--log-level', self.params['log_level']]
  1045. def addparam_memory(self, c):
  1046. return c + ['--memory', self.params['memory']]
  1047. def addparam_memory_reservation(self, c):
  1048. return c + ['--memory-reservation', self.params['memory_reservation']]
  1049. def addparam_memory_swap(self, c):
  1050. return c + ['--memory-swap', self.params['memory_swap']]
  1051. def addparam_memory_swappiness(self, c):
  1052. return c + ['--memory-swappiness', self.params['memory_swappiness']]
  1053. def addparam_mount(self, c):
  1054. return c + ['--mount', self.params['mount']]
  1055. def addparam_network(self, c):
  1056. return c + ['--network', ",".join(self.params['network'])]
  1057. def addparam_no_hosts(self, c):
  1058. return c + ['--no-hosts=%s' % self.params['no_hosts']]
  1059. def addparam_oom_kill_disable(self, c):
  1060. return c + ['--oom-kill-disable=%s' % self.params['oom_kill_disable']]
  1061. def addparam_oom_score_adj(self, c):
  1062. return c + ['--oom-score-adj', self.params['oom_score_adj']]
  1063. def addparam_pid(self, c):
  1064. return c + ['--pid', self.params['pid']]
  1065. def addparam_pids_limit(self, c):
  1066. return c + ['--pids-limit', self.params['pids_limit']]
  1067. def addparam_pod(self, c):
  1068. return c + ['--pod', self.params['pod']]
  1069. def addparam_privileged(self, c):
  1070. return c + ['--privileged=%s' % self.params['privileged']]
  1071. def addparam_publish(self, c):
  1072. for pub in self.params['publish']:
  1073. c += ['--publish', pub]
  1074. return c
  1075. def addparam_publish_all(self, c):
  1076. return c + ['--publish-all=%s' % self.params['publish_all']]
  1077. def addparam_read_only(self, c):
  1078. return c + ['--read-only=%s' % self.params['read_only']]
  1079. def addparam_read_only_tmpfs(self, c):
  1080. return c + ['--read-only-tmpfs=%s' % self.params['read_only_tmpfs']]
  1081. def addparam_restart_policy(self, c):
  1082. return c + ['--restart=%s' % self.params['restart_policy']]
  1083. def addparam_rm(self, c):
  1084. if self.params['rm']:
  1085. c += ['--rm']
  1086. return c
  1087. def addparam_rootfs(self, c):
  1088. return c + ['--rootfs=%s' % self.params['rootfs']]
  1089. def addparam_security_opt(self, c):
  1090. for secopt in self.params['security_opt']:
  1091. c += ['--security-opt', secopt]
  1092. return c
  1093. def addparam_shm_size(self, c):
  1094. return c + ['--shm-size', self.params['shm_size']]
  1095. def addparam_sig_proxy(self, c):
  1096. return c + ['--sig-proxy=%s' % self.params['sig_proxy']]
  1097. def addparam_stop_signal(self, c):
  1098. return c + ['--stop-signal', self.params['stop_signal']]
  1099. def addparam_stop_timeout(self, c):
  1100. return c + ['--stop-timeout', self.params['stop_timeout']]
  1101. def addparam_subgidname(self, c):
  1102. return c + ['--subgidname', self.params['subgidname']]
  1103. def addparam_subuidname(self, c):
  1104. return c + ['--subuidname', self.params['subuidname']]
  1105. def addparam_sysctl(self, c):
  1106. for sysctl in self.params['sysctl'].items():
  1107. c += ['--sysctl',
  1108. b"=".join([to_bytes(k, errors='surrogate_or_strict')
  1109. for k in sysctl])]
  1110. return c
  1111. def addparam_systemd(self, c):
  1112. return c + ['--systemd=%s' % self.params['systemd']]
  1113. def addparam_tmpfs(self, c):
  1114. for tmpfs in self.params['tmpfs'].items():
  1115. c += ['--tmpfs', ':'.join(tmpfs)]
  1116. return c
  1117. def addparam_tty(self, c):
  1118. return c + ['--tty=%s' % self.params['tty']]
  1119. def addparam_uidmap(self, c):
  1120. for uidmap in self.params['uidmap']:
  1121. c += ['--uidmap', uidmap]
  1122. return c
  1123. def addparam_ulimit(self, c):
  1124. for u in self.params['ulimit']:
  1125. c += ['--ulimit', u]
  1126. return c
  1127. def addparam_user(self, c):
  1128. return c + ['--user', self.params['user']]
  1129. def addparam_userns(self, c):
  1130. return c + ['--userns', self.params['userns']]
  1131. def addparam_uts(self, c):
  1132. return c + ['--uts', self.params['uts']]
  1133. def addparam_volume(self, c):
  1134. for vol in self.params['volume']:
  1135. if vol:
  1136. c += ['--volume', vol]
  1137. return c
  1138. def addparam_volumes_from(self, c):
  1139. for vol in self.params['volumes_from']:
  1140. c += ['--volumes-from', vol]
  1141. return c
  1142. def addparam_workdir(self, c):
  1143. return c + ['--workdir', self.params['workdir']]
  1144. # Add your own args for podman command
  1145. def addparam_cmd_args(self, c):
  1146. return c + self.params['cmd_args']
  1147. class PodmanDefaults:
  1148. def __init__(self, module, image_info, podman_version):
  1149. self.module = module
  1150. self.version = podman_version
  1151. self.image_info = image_info
  1152. self.defaults = {
  1153. "blkio_weight": 0,
  1154. "cgroups": "default",
  1155. "cidfile": "",
  1156. "cpus": 0.0,
  1157. "cpu_shares": 0,
  1158. "cpu_quota": 0,
  1159. "cpu_period": 0,
  1160. "cpu_rt_runtime": 0,
  1161. "cpu_rt_period": 0,
  1162. "cpuset_cpus": "",
  1163. "cpuset_mems": "",
  1164. "detach": True,
  1165. "device": [],
  1166. "env_host": False,
  1167. "etc_hosts": {},
  1168. "group_add": [],
  1169. "ipc": "",
  1170. "kernelmemory": "0",
  1171. "log_driver": "k8s-file",
  1172. "log_level": "error",
  1173. "memory": "0",
  1174. "memory_swap": "0",
  1175. "memory_reservation": "0",
  1176. # "memory_swappiness": -1,
  1177. "no_hosts": False,
  1178. # libpod issue with networks in inspection
  1179. "network": ["default"],
  1180. "oom_score_adj": 0,
  1181. "pid": "",
  1182. "privileged": False,
  1183. "rm": False,
  1184. "security_opt": [],
  1185. "stop_signal": self.image_info['config'].get('stopsignal', "15"),
  1186. "tty": False,
  1187. "user": self.image_info.get('user', ''),
  1188. "workdir": self.image_info['config'].get('workingdir', '/'),
  1189. "uts": "",
  1190. }
  1191. def default_dict(self):
  1192. # make here any changes to self.defaults related to podman version
  1193. # https://github.com/containers/libpod/pull/5669
  1194. if (LooseVersion(self.version) >= LooseVersion('1.8.0')
  1195. and LooseVersion(self.version) < LooseVersion('1.9.0')):
  1196. self.defaults['cpu_shares'] = 1024
  1197. if (LooseVersion(self.version) >= LooseVersion('2.0.0')):
  1198. self.defaults['network'] = ["slirp4netns"]
  1199. self.defaults['ipc'] = "private"
  1200. self.defaults['uts'] = "private"
  1201. self.defaults['pid'] = "private"
  1202. return self.defaults
  1203. class PodmanContainerDiff:
  1204. def __init__(self, module, info, image_info, podman_version):
  1205. self.module = module
  1206. self.version = podman_version
  1207. self.default_dict = None
  1208. self.info = yaml.safe_load(json.dumps(info).lower())
  1209. self.image_info = yaml.safe_load(json.dumps(image_info).lower())
  1210. self.params = self.defaultize()
  1211. self.diff = {'before': {}, 'after': {}}
  1212. self.non_idempotent = {
  1213. 'env_file', # We can't get env vars from file to check
  1214. 'env_host',
  1215. }
  1216. def defaultize(self):
  1217. params_with_defaults = {}
  1218. self.default_dict = PodmanDefaults(
  1219. self.module, self.image_info, self.version).default_dict()
  1220. for p in self.module.params:
  1221. if self.module.params[p] is None and p in self.default_dict:
  1222. params_with_defaults[p] = self.default_dict[p]
  1223. else:
  1224. params_with_defaults[p] = self.module.params[p]
  1225. return params_with_defaults
  1226. def _diff_update_and_compare(self, param_name, before, after):
  1227. if before != after:
  1228. self.diff['before'].update({param_name: before})
  1229. self.diff['after'].update({param_name: after})
  1230. return True
  1231. return False
  1232. def diffparam_annotation(self):
  1233. before = self.info['config']['annotations'] or {}
  1234. after = before.copy()
  1235. if self.module.params['annotation'] is not None:
  1236. after.update(self.params['annotation'])
  1237. return self._diff_update_and_compare('annotation', before, after)
  1238. def diffparam_env_host(self):
  1239. # It's impossible to get from inspest, recreate it if not default
  1240. before = False
  1241. after = self.params['env_host']
  1242. return self._diff_update_and_compare('env_host', before, after)
  1243. def diffparam_blkio_weight(self):
  1244. before = self.info['hostconfig']['blkioweight']
  1245. after = self.params['blkio_weight']
  1246. return self._diff_update_and_compare('blkio_weight', before, after)
  1247. def diffparam_blkio_weight_device(self):
  1248. before = self.info['hostconfig']['blkioweightdevice']
  1249. if before == [] and self.module.params['blkio_weight_device'] is None:
  1250. after = []
  1251. else:
  1252. after = self.params['blkio_weight_device']
  1253. return self._diff_update_and_compare('blkio_weight_device', before, after)
  1254. def diffparam_cap_add(self):
  1255. before = self.info['effectivecaps'] or []
  1256. after = []
  1257. if self.module.params['cap_add'] is not None:
  1258. after += ["cap_" + i.lower()
  1259. for i in self.module.params['cap_add']]
  1260. after += before
  1261. before, after = sorted(list(set(before))), sorted(list(set(after)))
  1262. return self._diff_update_and_compare('cap_add', before, after)
  1263. def diffparam_cap_drop(self):
  1264. before = self.info['effectivecaps'] or []
  1265. after = before[:]
  1266. if self.module.params['cap_drop'] is not None:
  1267. for c in ["cap_" + i.lower() for i in self.module.params['cap_drop']]:
  1268. if c in after:
  1269. after.remove(c)
  1270. before, after = sorted(list(set(before))), sorted(list(set(after)))
  1271. return self._diff_update_and_compare('cap_drop', before, after)
  1272. def diffparam_cgroup_parent(self):
  1273. before = self.info['hostconfig']['cgroupparent']
  1274. after = self.params['cgroup_parent']
  1275. if after is None:
  1276. after = before
  1277. return self._diff_update_and_compare('cgroup_parent', before, after)
  1278. def diffparam_cgroups(self):
  1279. # Cgroups output is not supported in all versions
  1280. if 'cgroups' in self.info['hostconfig']:
  1281. before = self.info['hostconfig']['cgroups']
  1282. after = self.params['cgroups']
  1283. return self._diff_update_and_compare('cgroups', before, after)
  1284. return False
  1285. def diffparam_cidfile(self):
  1286. before = self.info['hostconfig']['containeridfile']
  1287. after = self.params['cidfile']
  1288. return self._diff_update_and_compare('cidfile', before, after)
  1289. def diffparam_command(self):
  1290. # TODO(sshnaidm): to inspect image to get the default command
  1291. if self.module.params['command'] is not None:
  1292. before = self.info['config']['cmd']
  1293. after = self.params['command']
  1294. if isinstance(after, str):
  1295. after = [i.lower() for i in after.split()]
  1296. elif isinstance(after, list):
  1297. after = [i.lower() for i in after]
  1298. return self._diff_update_and_compare('command', before, after)
  1299. return False
  1300. def diffparam_conmon_pidfile(self):
  1301. before = self.info['conmonpidfile']
  1302. if self.module.params['conmon_pidfile'] is None:
  1303. after = before
  1304. else:
  1305. after = self.params['conmon_pidfile']
  1306. return self._diff_update_and_compare('conmon_pidfile', before, after)
  1307. def diffparam_cpu_period(self):
  1308. before = self.info['hostconfig']['cpuperiod']
  1309. after = self.params['cpu_period']
  1310. return self._diff_update_and_compare('cpu_period', before, after)
  1311. def diffparam_cpu_rt_period(self):
  1312. before = self.info['hostconfig']['cpurealtimeperiod']
  1313. after = self.params['cpu_rt_period']
  1314. return self._diff_update_and_compare('cpu_rt_period', before, after)
  1315. def diffparam_cpu_rt_runtime(self):
  1316. before = self.info['hostconfig']['cpurealtimeruntime']
  1317. after = self.params['cpu_rt_runtime']
  1318. return self._diff_update_and_compare('cpu_rt_runtime', before, after)
  1319. def diffparam_cpu_shares(self):
  1320. before = self.info['hostconfig']['cpushares']
  1321. after = self.params['cpu_shares']
  1322. return self._diff_update_and_compare('cpu_shares', before, after)
  1323. def diffparam_cpus(self):
  1324. before = int(self.info['hostconfig']['nanocpus']) / 1000000000
  1325. after = self.params['cpus']
  1326. return self._diff_update_and_compare('cpus', before, after)
  1327. def diffparam_cpuset_cpus(self):
  1328. before = self.info['hostconfig']['cpusetcpus']
  1329. after = self.params['cpuset_cpus']
  1330. return self._diff_update_and_compare('cpuset_cpus', before, after)
  1331. def diffparam_cpuset_mems(self):
  1332. before = self.info['hostconfig']['cpusetmems']
  1333. after = self.params['cpuset_mems']
  1334. return self._diff_update_and_compare('cpuset_mems', before, after)
  1335. def diffparam_device(self):
  1336. before = [":".join([i['pathonhost'], i['pathincontainer']])
  1337. for i in self.info['hostconfig']['devices']]
  1338. after = [":".join(i.split(":")[:2]) for i in self.params['device']]
  1339. before, after = sorted(list(set(before))), sorted(list(set(after)))
  1340. return self._diff_update_and_compare('devices', before, after)
  1341. def diffparam_device_read_bps(self):
  1342. before = self.info['hostconfig']['blkiodevicereadbps'] or []
  1343. before = ["%s:%s" % (i['path'], i['rate']) for i in before]
  1344. after = self.params['device_read_bps'] or []
  1345. before, after = sorted(list(set(before))), sorted(list(set(after)))
  1346. return self._diff_update_and_compare('device_read_bps', before, after)
  1347. def diffparam_device_read_iops(self):
  1348. before = self.info['hostconfig']['blkiodevicereadiops'] or []
  1349. before = ["%s:%s" % (i['path'], i['rate']) for i in before]
  1350. after = self.params['device_read_iops'] or []
  1351. before, after = sorted(list(set(before))), sorted(list(set(after)))
  1352. return self._diff_update_and_compare('device_read_iops', before, after)
  1353. def diffparam_device_write_bps(self):
  1354. before = self.info['hostconfig']['blkiodevicewritebps'] or []
  1355. before = ["%s:%s" % (i['path'], i['rate']) for i in before]
  1356. after = self.params['device_write_bps'] or []
  1357. before, after = sorted(list(set(before))), sorted(list(set(after)))
  1358. return self._diff_update_and_compare('device_write_bps', before, after)
  1359. def diffparam_device_write_iops(self):
  1360. before = self.info['hostconfig']['blkiodevicewriteiops'] or []
  1361. before = ["%s:%s" % (i['path'], i['rate']) for i in before]
  1362. after = self.params['device_write_iops'] or []
  1363. before, after = sorted(list(set(before))), sorted(list(set(after)))
  1364. return self._diff_update_and_compare('device_write_iops', before, after)
  1365. # Limited idempotency, it can't guess default values
  1366. def diffparam_env(self):
  1367. env_before = self.info['config']['env'] or {}
  1368. before = {i.split("=")[0]: i.split("=")[1] for i in env_before}
  1369. after = before.copy()
  1370. if self.params['env']:
  1371. after.update({
  1372. str(k).lower(): str(v).lower()
  1373. for k, v in self.params['env'].items()
  1374. })
  1375. return self._diff_update_and_compare('env', before, after)
  1376. def diffparam_etc_hosts(self):
  1377. if self.info['hostconfig']['extrahosts']:
  1378. before = dict([i.split(":") for i in self.info['hostconfig']['extrahosts']])
  1379. else:
  1380. before = {}
  1381. after = self.params['etc_hosts']
  1382. return self._diff_update_and_compare('etc_hosts', before, after)
  1383. def diffparam_group_add(self):
  1384. before = self.info['hostconfig']['groupadd']
  1385. after = self.params['group_add']
  1386. return self._diff_update_and_compare('group_add', before, after)
  1387. # Healthcheck is only defined in container config if a healthcheck
  1388. # was configured; otherwise the config key isn't part of the config.
  1389. def diffparam_healthcheck(self):
  1390. if 'healthcheck' in self.info['config']:
  1391. # the "test" key is a list of 2 items where the first one is
  1392. # "CMD-SHELL" and the second one is the actual healthcheck command.
  1393. before = self.info['config']['healthcheck']['test'][1]
  1394. else:
  1395. before = ''
  1396. after = self.params['healthcheck'] or before
  1397. return self._diff_update_and_compare('healthcheck', before, after)
  1398. # Because of hostname is random generated, this parameter has partial idempotency only.
  1399. def diffparam_hostname(self):
  1400. before = self.info['config']['hostname']
  1401. after = self.params['hostname'] or before
  1402. return self._diff_update_and_compare('hostname', before, after)
  1403. def diffparam_image(self):
  1404. # TODO(sshnaidm): for strict image compare mode use SHAs
  1405. before = self.info['config']['image']
  1406. after = self.params['image']
  1407. mode = self.params['image_strict']
  1408. if mode is None or not mode:
  1409. # In a idempotency 'lite mode' assume all images from different registries are the same
  1410. before = before.replace(":latest", "")
  1411. after = after.replace(":latest", "")
  1412. before = before.split("/")[-1]
  1413. after = after.split("/")[-1]
  1414. return self._diff_update_and_compare('image', before, after)
  1415. def diffparam_ipc(self):
  1416. before = self.info['hostconfig']['ipcmode']
  1417. after = self.params['ipc']
  1418. if self.params['pod'] and not after:
  1419. after = before
  1420. return self._diff_update_and_compare('ipc', before, after)
  1421. def diffparam_label(self):
  1422. before = self.info['config']['labels'] or {}
  1423. after = self.image_info.get('labels') or {}
  1424. if self.params['label']:
  1425. after.update({
  1426. str(k).lower(): str(v).lower()
  1427. for k, v in self.params['label'].items()
  1428. })
  1429. return self._diff_update_and_compare('label', before, after)
  1430. def diffparam_log_driver(self):
  1431. before = self.info['hostconfig']['logconfig']['type']
  1432. after = self.params['log_driver']
  1433. return self._diff_update_and_compare('log_driver', before, after)
  1434. def diffparam_log_level(self):
  1435. excom = self.info['exitcommand']
  1436. if '--log-level' in excom:
  1437. before = excom[excom.index('--log-level') + 1]
  1438. else:
  1439. before = self.params['log_level']
  1440. after = self.params['log_level']
  1441. return self._diff_update_and_compare('log_level', before, after)
  1442. # Parameter has limited idempotency, unable to guess the default log_path
  1443. def diffparam_log_opt(self):
  1444. before = self.info['logpath']
  1445. if self.module.params['log_opt'] in [None, '']:
  1446. after = before
  1447. else:
  1448. after = self.params['log_opt'].split("=")[1]
  1449. return self._diff_update_and_compare('log_opt', before, after)
  1450. def diffparam_memory(self):
  1451. before = str(self.info['hostconfig']['memory'])
  1452. after = self.params['memory']
  1453. return self._diff_update_and_compare('memory', before, after)
  1454. def diffparam_memory_swap(self):
  1455. # By default it's twice memory parameter
  1456. before = str(self.info['hostconfig']['memoryswap'])
  1457. after = self.params['memory_swap']
  1458. if (self.module.params['memory_swap'] is None
  1459. and self.params['memory'] != 0
  1460. and self.params['memory'].isdigit()):
  1461. after = str(int(self.params['memory']) * 2)
  1462. return self._diff_update_and_compare('memory_swap', before, after)
  1463. def diffparam_memory_reservation(self):
  1464. before = str(self.info['hostconfig']['memoryreservation'])
  1465. after = self.params['memory_reservation']
  1466. return self._diff_update_and_compare('memory_reservation', before, after)
  1467. def diffparam_network(self):
  1468. before = [self.info['hostconfig']['networkmode']]
  1469. after = self.params['network']
  1470. if self.params['pod'] and not self.module.params['network']:
  1471. after = before
  1472. return self._diff_update_and_compare('network', before, after)
  1473. def diffparam_no_hosts(self):
  1474. before = not bool(self.info['hostspath'])
  1475. after = self.params['no_hosts']
  1476. if self.params['network'] == ['none']:
  1477. after = True
  1478. return self._diff_update_and_compare('no_hosts', before, after)
  1479. def diffparam_oom_score_adj(self):
  1480. before = self.info['hostconfig']['oomscoreadj']
  1481. after = self.params['oom_score_adj']
  1482. return self._diff_update_and_compare('oom_score_adj', before, after)
  1483. def diffparam_privileged(self):
  1484. before = self.info['hostconfig']['privileged']
  1485. after = self.params['privileged']
  1486. return self._diff_update_and_compare('privileged', before, after)
  1487. def diffparam_pid(self):
  1488. before = self.info['hostconfig']['pidmode']
  1489. after = self.params['pid']
  1490. return self._diff_update_and_compare('pid', before, after)
  1491. # TODO(sshnaidm) Need to add port ranges support
  1492. def diffparam_publish(self):
  1493. ports = self.info['hostconfig']['portbindings']
  1494. before = [":".join([
  1495. j[0]['hostip'],
  1496. str(j[0]["hostport"]),
  1497. i.replace('/tcp', '')
  1498. ]).strip(':') for i, j in ports.items()]
  1499. after = self.params['publish'] or []
  1500. if self.params['publish_all']:
  1501. image_ports = self.image_info['config'].get('exposedports', {})
  1502. if image_ports:
  1503. after += list(image_ports.keys())
  1504. after = [i.replace("/tcp", "") for i in after]
  1505. # No support for port ranges yet
  1506. for ports in after:
  1507. if "-" in ports:
  1508. return self._diff_update_and_compare('publish', '', '')
  1509. before, after = sorted(list(set(before))), sorted(list(set(after)))
  1510. return self._diff_update_and_compare('publish', before, after)
  1511. def diffparam_rm(self):
  1512. before = self.info['hostconfig']['autoremove']
  1513. after = self.params['rm']
  1514. return self._diff_update_and_compare('rm', before, after)
  1515. def diffparam_security_opt(self):
  1516. before = self.info['hostconfig']['securityopt']
  1517. after = self.params['security_opt']
  1518. before, after = sorted(list(set(before))), sorted(list(set(after)))
  1519. return self._diff_update_and_compare('security_opt', before, after)
  1520. def diffparam_stop_signal(self):
  1521. signals = {
  1522. "sighup": "1",
  1523. "sigint": "2",
  1524. "sigquit": "3",
  1525. "sigill": "4",
  1526. "sigtrap": "5",
  1527. "sigabrt": "6",
  1528. "sigiot": "6",
  1529. "sigbus": "7",
  1530. "sigfpe": "8",
  1531. "sigkill": "9",
  1532. "sigusr1": "10",
  1533. "sigsegv": "11",
  1534. "sigusr2": "12",
  1535. "sigpipe": "13",
  1536. "sigalrm": "14",
  1537. "sigterm": "15",
  1538. "sigstkflt": "16",
  1539. "sigchld": "17",
  1540. "sigcont": "18",
  1541. "sigstop": "19",
  1542. "sigtstp": "20",
  1543. "sigttin": "21",
  1544. "sigttou": "22",
  1545. "sigurg": "23",
  1546. "sigxcpu": "24",
  1547. "sigxfsz": "25",
  1548. "sigvtalrm": "26",
  1549. "sigprof": "27",
  1550. "sigwinch": "28",
  1551. "sigio": "29",
  1552. "sigpwr": "30",
  1553. "sigsys": "31"
  1554. }
  1555. before = str(self.info['config']['stopsignal'])
  1556. if not before.isdigit():
  1557. before = signals[before]
  1558. after = str(self.params['stop_signal'])
  1559. if not after.isdigit():
  1560. after = signals[after]
  1561. return self._diff_update_and_compare('stop_signal', before, after)
  1562. def diffparam_tty(self):
  1563. before = self.info['config']['tty']
  1564. after = self.params['tty']
  1565. return self._diff_update_and_compare('tty', before, after)
  1566. def diffparam_user(self):
  1567. before = self.info['config']['user']
  1568. after = self.params['user']
  1569. return self._diff_update_and_compare('user', before, after)
  1570. def diffparam_ulimit(self):
  1571. after = self.params['ulimit'] or []
  1572. # In case of latest podman
  1573. if 'createcommand' in self.info['config']:
  1574. ulimits = []
  1575. for k, c in enumerate(self.info['config']['createcommand']):
  1576. if c == '--ulimit':
  1577. ulimits.append(self.info['config']['createcommand'][k + 1])
  1578. before = ulimits
  1579. before, after = sorted(before), sorted(after)
  1580. return self._diff_update_and_compare('ulimit', before, after)
  1581. if after:
  1582. ulimits = self.info['hostconfig']['ulimits']
  1583. before = {
  1584. u['name'].replace('rlimit_', ''): "%s:%s" % (u['soft'], u['hard']) for u in ulimits}
  1585. after = {i.split('=')[0]: i.split('=')[1] for i in self.params['ulimit']}
  1586. new_before = []
  1587. new_after = []
  1588. for u in list(after.keys()):
  1589. # We don't support unlimited ulimits because it depends on platform
  1590. if u in before and "-1" not in after[u]:
  1591. new_before.append([u, before[u]])
  1592. new_after.append([u, after[u]])
  1593. return self._diff_update_and_compare('ulimit', new_before, new_after)
  1594. return self._diff_update_and_compare('ulimit', '', '')
  1595. def diffparam_uts(self):
  1596. before = self.info['hostconfig']['utsmode']
  1597. after = self.params['uts']
  1598. if self.params['pod'] and not after:
  1599. after = before
  1600. return self._diff_update_and_compare('uts', before, after)
  1601. def diffparam_volume(self):
  1602. def clean_volume(x):
  1603. '''Remove trailing and double slashes from volumes.'''
  1604. return x.replace("//", "/").rstrip("/")
  1605. before = self.info['mounts']
  1606. before_local_vols = []
  1607. if before:
  1608. volumes = []
  1609. local_vols = []
  1610. for m in before:
  1611. if m['type'] != 'volume':
  1612. volumes.append([m['source'], m['destination']])
  1613. elif m['type'] == 'volume':
  1614. local_vols.append([m['name'], m['destination']])
  1615. before = [":".join(v) for v in volumes]
  1616. before_local_vols = [":".join(v) for v in local_vols]
  1617. if self.params['volume'] is not None:
  1618. after = [":".join(
  1619. [clean_volume(i) for i in v.split(":")[:2]]
  1620. ) for v in self.params['volume']]
  1621. else:
  1622. after = []
  1623. if before_local_vols:
  1624. after = list(set(after).difference(before_local_vols))
  1625. before, after = sorted(list(set(before))), sorted(list(set(after)))
  1626. return self._diff_update_and_compare('volume', before, after)
  1627. def diffparam_volumes_from(self):
  1628. # Possibly volumesfrom is not in config
  1629. before = self.info['hostconfig'].get('volumesfrom', []) or []
  1630. after = self.params['volumes_from'] or []
  1631. return self._diff_update_and_compare('volumes_from', before, after)
  1632. def diffparam_workdir(self):
  1633. before = self.info['config']['workingdir']
  1634. after = self.params['workdir']
  1635. return self._diff_update_and_compare('workdir', before, after)
  1636. def is_different(self):
  1637. diff_func_list = [func for func in dir(self)
  1638. if callable(getattr(self, func)) and func.startswith(
  1639. "diffparam")]
  1640. fail_fast = not bool(self.module._diff)
  1641. different = False
  1642. for func_name in diff_func_list:
  1643. dff_func = getattr(self, func_name)
  1644. if dff_func():
  1645. if fail_fast:
  1646. return True
  1647. else:
  1648. different = True
  1649. # Check non idempotent parameters
  1650. for p in self.non_idempotent:
  1651. if self.module.params[p] is not None and self.module.params[p] not in [{}, [], '']:
  1652. different = True
  1653. return different
  1654. def ensure_image_exists(module, image):
  1655. """If image is passed, ensure it exists, if not - pull it or fail.
  1656. Arguments:
  1657. module {obj} -- ansible module object
  1658. image {str} -- name of image
  1659. Returns:
  1660. list -- list of image actions - if it pulled or nothing was done
  1661. """
  1662. image_actions = []
  1663. module_exec = module.params['executable']
  1664. if not image:
  1665. return image_actions
  1666. rc, out, err = module.run_command([module_exec, 'image', 'exists', image])
  1667. if rc == 0:
  1668. return image_actions
  1669. rc, out, err = module.run_command([module_exec, 'image', 'pull', image])
  1670. if rc != 0:
  1671. module.fail_json(msg="Can't pull image %s" % image, stdout=out,
  1672. stderr=err)
  1673. image_actions.append("pulled image %s" % image)
  1674. return image_actions
  1675. class PodmanContainer:
  1676. """Perform container tasks.
  1677. Manages podman container, inspects it and checks its current state
  1678. """
  1679. def __init__(self, module, name):
  1680. """Initialize PodmanContainer class.
  1681. Arguments:
  1682. module {obj} -- ansible module object
  1683. name {str} -- name of container
  1684. """
  1685. super(PodmanContainer, self).__init__()
  1686. self.module = module
  1687. self.name = name
  1688. self.stdout, self.stderr = '', ''
  1689. self.info = self.get_info()
  1690. self.version = self._get_podman_version()
  1691. self.diff = {}
  1692. self.actions = []
  1693. @property
  1694. def exists(self):
  1695. """Check if container exists."""
  1696. return bool(self.info != {})
  1697. @property
  1698. def different(self):
  1699. """Check if container is different."""
  1700. diffcheck = PodmanContainerDiff(
  1701. self.module,
  1702. self.info,
  1703. self.get_image_info(),
  1704. self.version)
  1705. is_different = diffcheck.is_different()
  1706. diffs = diffcheck.diff
  1707. if self.module._diff and is_different and diffs['before'] and diffs['after']:
  1708. self.diff['before'] = "\n".join(
  1709. ["%s - %s" % (k, v) for k, v in sorted(
  1710. diffs['before'].items())]) + "\n"
  1711. self.diff['after'] = "\n".join(
  1712. ["%s - %s" % (k, v) for k, v in sorted(
  1713. diffs['after'].items())]) + "\n"
  1714. return is_different
  1715. @property
  1716. def running(self):
  1717. """Return True if container is running now."""
  1718. return self.exists and self.info['State']['Running']
  1719. @property
  1720. def stopped(self):
  1721. """Return True if container exists and is not running now."""
  1722. return self.exists and not self.info['State']['Running']
  1723. def get_info(self):
  1724. """Inspect container and gather info about it."""
  1725. # pylint: disable=unused-variable
  1726. rc, out, err = self.module.run_command(
  1727. [self.module.params['executable'], b'container', b'inspect', self.name])
  1728. return json.loads(out)[0] if rc == 0 else {}
  1729. def get_image_info(self):
  1730. """Inspect container image and gather info about it."""
  1731. # pylint: disable=unused-variable
  1732. rc, out, err = self.module.run_command(
  1733. [self.module.params['executable'], b'image', b'inspect', self.module.params['image']])
  1734. return json.loads(out)[0] if rc == 0 else {}
  1735. def _get_podman_version(self):
  1736. # pylint: disable=unused-variable
  1737. rc, out, err = self.module.run_command(
  1738. [self.module.params['executable'], b'--version'])
  1739. if rc != 0 or not out or "version" not in out:
  1740. self.module.fail_json(msg="%s run failed!" % self.module.params['executable'])
  1741. return out.split("version")[1].strip()
  1742. def _perform_action(self, action):
  1743. """Perform action with container.
  1744. Arguments:
  1745. action {str} -- action to perform - start, create, stop, run,
  1746. delete
  1747. """
  1748. def clean_stderr(err):
  1749. # Inspect STDERR for logs to avoid modules failures in case of
  1750. # increased log level verbosity
  1751. return "\n".join(
  1752. [line for line in err.splitlines() if 'level=' not in line]).strip()
  1753. b_command = PodmanModuleParams(action,
  1754. self.module.params,
  1755. self.version,
  1756. self.module,
  1757. ).construct_command_from_params()
  1758. full_cmd = " ".join([self.module.params['executable']]
  1759. + [to_native(i) for i in b_command])
  1760. self.actions.append(full_cmd)
  1761. if self.module.check_mode:
  1762. self.module.log("PODMAN-CONTAINER-DEBUG (check_mode): %s" % full_cmd)
  1763. else:
  1764. rc, out, err = self.module.run_command(
  1765. [self.module.params['executable'], b'container'] + b_command,
  1766. expand_user_and_vars=False)
  1767. self.module.log("PODMAN-CONTAINER-DEBUG: %s" % full_cmd)
  1768. if self.module.params['debug']:
  1769. self.module.log("PODMAN-CONTAINER-DEBUG STDOUT: %s" % out)
  1770. self.module.log("PODMAN-CONTAINER-DEBUG STDERR: %s" % err)
  1771. self.module.log("PODMAN-CONTAINER-DEBUG RC: %s" % rc)
  1772. self.stdout = out
  1773. self.stderr = err
  1774. if rc != 0 or clean_stderr(err) != '':
  1775. self.module.fail_json(
  1776. msg="Failed %s container %s" % (action, self.name),
  1777. stdout=out, stderr=err)
  1778. def run(self):
  1779. """Run the container."""
  1780. self._perform_action('run')
  1781. def delete(self):
  1782. """Delete the container."""
  1783. self._perform_action('delete')
  1784. def stop(self):
  1785. """Stop the container."""
  1786. self._perform_action('stop')
  1787. def start(self):
  1788. """Start the container."""
  1789. self._perform_action('start')
  1790. def create(self):
  1791. """Create the container."""
  1792. self._perform_action('create')
  1793. def recreate(self):
  1794. """Recreate the container."""
  1795. self.delete()
  1796. self.run()
  1797. def restart(self):
  1798. """Restart the container."""
  1799. self.stop()
  1800. self.run()
  1801. class PodmanManager:
  1802. """Module manager class.
  1803. Defines according to parameters what actions should be applied to container
  1804. """
  1805. def __init__(self, module):
  1806. """Initialize PodmanManager class.
  1807. Arguments:
  1808. module {obj} -- ansible module object
  1809. """
  1810. super(PodmanManager, self).__init__()
  1811. self.module = module
  1812. self.results = {
  1813. 'changed': False,
  1814. 'actions': [],
  1815. 'container': {},
  1816. }
  1817. self.name = self.module.params['name']
  1818. self.executable = \
  1819. self.module.get_bin_path(self.module.params['executable'],
  1820. required=True)
  1821. self.image = self.module.params['image']
  1822. image_actions = ensure_image_exists(self.module, self.image)
  1823. self.results['actions'] += image_actions
  1824. self.state = self.module.params['state']
  1825. self.restart = self.module.params['force_restart']
  1826. self.recreate = self.module.params['recreate']
  1827. self.container = PodmanContainer(self.module, self.name)
  1828. def update_container_result(self, changed=True):
  1829. """Inspect the current container, update results with last info, exit.
  1830. Keyword Arguments:
  1831. changed {bool} -- whether any action was performed
  1832. (default: {True})
  1833. """
  1834. facts = self.container.get_info() if changed else self.container.info
  1835. out, err = self.container.stdout, self.container.stderr
  1836. self.results.update({'changed': changed, 'container': facts,
  1837. 'podman_actions': self.container.actions},
  1838. stdout=out, stderr=err)
  1839. if self.container.diff:
  1840. self.results.update({'diff': self.container.diff})
  1841. if self.module.params['debug']:
  1842. self.results.update({'podman_version': self.container.version})
  1843. self.module.exit_json(**self.results)
  1844. def make_started(self):
  1845. """Run actions if desired state is 'started'."""
  1846. if self.container.running and \
  1847. (self.container.different or self.recreate):
  1848. self.container.recreate()
  1849. self.results['actions'].append('recreated %s' %
  1850. self.container.name)
  1851. self.update_container_result()
  1852. elif self.container.running and not self.container.different:
  1853. if self.restart:
  1854. self.container.restart()
  1855. self.results['actions'].append('restarted %s' %
  1856. self.container.name)
  1857. self.update_container_result()
  1858. self.update_container_result(changed=False)
  1859. elif not self.container.exists:
  1860. self.container.run()
  1861. self.results['actions'].append('started %s' % self.container.name)
  1862. self.update_container_result()
  1863. elif self.container.stopped and self.container.different:
  1864. self.container.recreate()
  1865. self.results['actions'].append('recreated %s' %
  1866. self.container.name)
  1867. self.update_container_result()
  1868. elif self.container.stopped and not self.container.different:
  1869. self.container.start()
  1870. self.results['actions'].append('started %s' % self.container.name)
  1871. self.update_container_result()
  1872. def make_stopped(self):
  1873. """Run actions if desired state is 'stopped'."""
  1874. if not self.container.exists and not self.image:
  1875. self.module.fail_json(msg='Cannot create container when image'
  1876. ' is not specified!')
  1877. if not self.container.exists:
  1878. self.container.create()
  1879. self.results['actions'].append('created %s' % self.container.name)
  1880. self.update_container_result()
  1881. if self.container.stopped:
  1882. self.update_container_result(changed=False)
  1883. elif self.container.running:
  1884. self.container.stop()
  1885. self.results['actions'].append('stopped %s' % self.container.name)
  1886. self.update_container_result()
  1887. def make_absent(self):
  1888. """Run actions if desired state is 'absent'."""
  1889. if not self.container.exists:
  1890. self.results.update({'changed': False})
  1891. elif self.container.exists:
  1892. self.container.delete()
  1893. self.results['actions'].append('deleted %s' % self.container.name)
  1894. self.results.update({'changed': True})
  1895. self.results.update({'container': {},
  1896. 'podman_actions': self.container.actions})
  1897. self.module.exit_json(**self.results)
  1898. def execute(self):
  1899. """Execute the desired action according to map of actions & states."""
  1900. states_map = {
  1901. 'present': self.make_started,
  1902. 'started': self.make_started,
  1903. 'absent': self.make_absent,
  1904. 'stopped': self.make_stopped
  1905. }
  1906. process_action = states_map[self.state]
  1907. process_action()
  1908. self.module.fail_json(msg="Unexpected logic error happened, "
  1909. "please contact maintainers ASAP!")
  1910. def main():
  1911. module = AnsibleModule(
  1912. argument_spec=yaml.safe_load(DOCUMENTATION)['options'],
  1913. mutually_exclusive=(
  1914. ['no_hosts', 'etc_hosts'],
  1915. ),
  1916. supports_check_mode=True,
  1917. )
  1918. # work on input vars
  1919. if module.params['state'] in ['started', 'present'] and \
  1920. not module.params['image']:
  1921. module.fail_json(msg="State '%s' required image to be configured!" %
  1922. module.params['state'])
  1923. PodmanManager(module).execute()
  1924. if __name__ == '__main__':
  1925. main()