Skip to content

Commit a7333cd

Browse files
authored
Adopt molecule-podman driver (#71)
1 parent 7fe6acc commit a7333cd

File tree

20 files changed

+763
-4
lines changed

20 files changed

+763
-4
lines changed

.github/workflows/tox.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ jobs:
3434
runs-on: ubuntu-22.04
3535
needs: pre
3636
env:
37-
PYTEST_REQPASS: 6
37+
PYTEST_REQPASS: 11
3838
strategy:
3939
fail-fast: false
4040
matrix: ${{ fromJson(needs.pre.outputs.matrix) }}

molecule/test-podman/converge.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
- name: Converge
3+
hosts: all
4+
tasks:
5+
- name: Test
6+
ansible.builtin.debug:
7+
msg: it worked!

molecule/test-podman/molecule.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
dependency:
3+
name: galaxy
4+
driver:
5+
name: podman
6+
platforms:
7+
- name: instance
8+
image: docker.io/pycontribs/centos:8
9+
pre_build_image: true
10+
published_ports:
11+
- 127.0.0.1:2080:80
12+
- 127.0.0.1:2443:443
13+
provisioner:
14+
name: ansible
15+
verifier:
16+
name: ansible

molecule/test-podman/verify.yml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
# This is an example playbook to execute Ansible tests.
3+
4+
- name: Verify
5+
hosts: all
6+
tasks:
7+
- name: Example assertion
8+
ansible.builtin.assert:
9+
that: true

pyproject.toml

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,13 +67,22 @@ docker = [
6767
]
6868
ec2 = []
6969
gce = []
70+
podman = [
71+
# selinux python module is needed as least by ansible-podman modules
72+
# and allows us of isolated (default) virtualenvs. It does not avoid need
73+
# to install the system selinux libraries but it will provide a clear
74+
# message when user has to do that.
75+
'selinux; sys_platform=="linux2"',
76+
'selinux; sys_platform=="linux"',
77+
]
7078

7179

7280
[project.entry-points."molecule.driver"]
7381
azure = "molecule_plugins.azure.driver:Azure"
7482
docker = "molecule_plugins.docker.driver:Docker"
7583
ec2 = "molecule_plugins.ec2.driver:EC2"
7684
gce = "molecule_plugins.gce.driver:GCE"
85+
podman = "molecule_plugins.podman.driver:Podman"
7786
vagrant = "molecule_plugins.vagrant.driver:Vagrant"
7887

7988
[tool.setuptools_scm]

requirements.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
collections:
2+
- name: ansible.posix # docker
3+
version: ">=1.4.0"
24
- name: google.cloud
35
- name: community.aws
46
- name: community.docker
57
version: ">=3.0.2"
6-
- name: ansible.posix # docker
7-
version: ">=1.4.0"
8+
- name: containers.podman
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Molecule Podman Driver."""
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"molecule_directory": "molecule",
3+
"role_name": "OVERRIDDEN",
4+
"scenario_name": "OVERRIDDEN"
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
- name: Converge
3+
hosts: all
4+
tasks:
5+
# replace these tasks with whatever you find suitable to test
6+
- name: Copy something to test use of synchronize module
7+
ansible.builtin.copy:
8+
src: /etc/hosts
9+
dest: /tmp/hosts-from-controller
10+
- name: "Include {{ cookiecutter.role_name }}"
11+
ansible.builtin.include_role:
12+
name: "{{ cookiecutter.role_name }}"

src/molecule_plugins/podman/driver.py

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
# Copyright (c) 2015-2018 Cisco Systems, Inc.
2+
#
3+
# Permission is hereby granted, free of charge, to any person obtaining a copy
4+
# of this software and associated documentation files (the "Software"), to
5+
# deal in the Software without restriction, including without limitation the
6+
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7+
# sell copies of the Software, and to permit persons to whom the Software is
8+
# furnished to do so, subject to the following conditions:
9+
#
10+
# The above copyright notice and this permission notice shall be included in
11+
# all copies or substantial portions of the Software.
12+
#
13+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18+
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19+
# DEALINGS IN THE SOFTWARE.
20+
"""Podman Driver Module."""
21+
22+
from __future__ import absolute_import
23+
24+
import os
25+
import warnings
26+
from shutil import which
27+
from typing import Dict
28+
29+
from ansible_compat.runtime import Runtime
30+
from molecule import logger, util
31+
from molecule.api import Driver, MoleculeRuntimeWarning
32+
from molecule.constants import RC_SETUP_ERROR
33+
from molecule.util import sysexit_with_message
34+
from packaging.version import Version
35+
36+
log = logger.get_logger(__name__)
37+
38+
39+
class Podman(Driver):
40+
"""
41+
The class responsible for managing `Podman`_ containers.
42+
43+
`Podman`_ is not default driver used in Molecule.
44+
45+
Molecule uses Podman ansible connector and podman CLI while mapping
46+
variables from ``molecule.yml`` into ``create.yml`` and ``destroy.yml``.
47+
48+
.. _`podman connection`: https://docs.ansible.com/ansible/latest/plugins/connection/podman.html
49+
50+
.. code-block:: yaml
51+
52+
driver:
53+
name: podman
54+
platforms:
55+
- name: instance
56+
hostname: instance
57+
image: image_name:tag
58+
dockerfile: Dockerfile.j2
59+
pull: True|False
60+
pre_build_image: True|False
61+
registry:
62+
url: registry.example.com
63+
credentials:
64+
username: $USERNAME
65+
password: $PASSWORD
66+
override_command: True|False
67+
command: sleep infinity
68+
tty: True|False
69+
pid_mode: host
70+
privileged: True|False
71+
security_opts:
72+
- seccomp=unconfined
73+
devices:
74+
- /dev/sdc:/dev/xvdc:rwm
75+
volumes:
76+
- /sys/fs/cgroup:/sys/fs/cgroup:ro
77+
tmpfs:
78+
- /tmp
79+
- /run
80+
capabilities:
81+
- SYS_ADMIN
82+
exposed_ports:
83+
- 53/udp
84+
- 53/tcp
85+
published_ports:
86+
- 0.0.0.0:8053:53/udp
87+
- 0.0.0.0:8053:53/tcp
88+
ulimits:
89+
- nofile=1024:1028
90+
dns_servers:
91+
- 8.8.8.8
92+
network: host
93+
etc_hosts: {'host1.example.com': '10.3.1.5'}
94+
cert_path: /foo/bar/cert.pem
95+
tls_verify: true
96+
env:
97+
FOO: bar
98+
restart_policy: on-failure
99+
restart_retries: 1
100+
buildargs:
101+
http_proxy: http://proxy.example.com:8080/
102+
cgroup_manager: cgroupfs
103+
storage_opt: overlay.mount_program=/usr/bin/fuse-overlayfs
104+
storage_driver: overlay
105+
systemd: true|false|always
106+
extra_opts:
107+
- --memory=128m
108+
109+
If specifying the `CMD`_ directive in your ``Dockerfile.j2`` or consuming a
110+
built image which declares a ``CMD`` directive, then you must set
111+
``override_command: False``. Otherwise, Molecule takes care to honour the
112+
value of the ``command`` key or uses the default of ``bash -c "while true;
113+
do sleep 10000; done"`` to run the container until it is provisioned.
114+
115+
When attempting to utilize a container image with `systemd`_ as your init
116+
system inside the container to simulate a real machine, make sure to set
117+
the ``privileged``, ``command``, and ``environment`` values. An example
118+
using the ``centos:8`` image is below:
119+
120+
.. note:: Do note that running containers in privileged mode is considerably
121+
less secure.
122+
123+
.. code-block:: yaml
124+
125+
platforms:
126+
- name: instance
127+
image: centos:8
128+
privileged: true
129+
command: "/usr/sbin/init"
130+
tty: True
131+
132+
.. code-block:: bash
133+
134+
$ python3 -m pip install molecule[podman]
135+
136+
When pulling from a private registry, it is the user's discretion to decide
137+
whether to use hard-code strings or environment variables for passing
138+
credentials to molecule.
139+
140+
.. important::
141+
142+
Hard-coded credentials in ``molecule.yml`` should be avoided, instead use
143+
`variable substitution`_.
144+
145+
Provide a list of files Molecule will preserve, relative to the scenario
146+
ephemeral directory, after any ``destroy`` subcommand execution.
147+
148+
.. code-block:: yaml
149+
150+
driver:
151+
name: podman
152+
safe_files:
153+
- foo
154+
155+
.. _`Podman`: https://podman.io/
156+
.. _`systemd`: https://www.freedesktop.org/wiki/Software/systemd/
157+
.. _`CMD`: https://docs.docker.com/engine/reference/builder/#cmd
158+
""" # noqa
159+
160+
def __init__(self, config=None):
161+
"""Construct Podman."""
162+
super().__init__(config)
163+
self._name = "podman"
164+
# To change the podman executable, set environment variable
165+
# MOLECULE_PODMAN_EXECUTABLE
166+
# An example could be MOLECULE_PODMAN_EXECUTABLE=podman-remote
167+
self.podman_exec = os.environ.get("MOLECULE_PODMAN_EXECUTABLE", "podman")
168+
self._podman_cmd = None
169+
self._sanity_passed = False
170+
171+
@property
172+
def podman_cmd(self):
173+
"""Lazily calculate the podman command."""
174+
if not self._podman_cmd:
175+
self._podman_cmd = which(self.podman_exec)
176+
if not self._podman_cmd:
177+
msg = f"command not found in PATH {self.podman_exec}"
178+
util.sysexit_with_message(msg)
179+
return self._podman_cmd
180+
181+
@property
182+
def name(self):
183+
return self._name
184+
185+
@name.setter
186+
def name(self, value):
187+
self._name = value
188+
189+
@property
190+
def login_cmd_template(self):
191+
return (
192+
f"{self.podman_cmd} exec "
193+
"-e COLUMNS={columns} "
194+
"-e LINES={lines} "
195+
"-e SHELL=bash "
196+
"-e TERM=xterm "
197+
"-ti {instance} bash"
198+
)
199+
200+
@property
201+
def default_safe_files(self):
202+
return [os.path.join(self._config.scenario.ephemeral_directory, "Dockerfile")]
203+
204+
@property
205+
def default_ssh_connection_options(self):
206+
return []
207+
208+
def login_options(self, instance_name):
209+
return {"instance": instance_name}
210+
211+
def ansible_connection_options(self, instance_name):
212+
return {
213+
"ansible_connection": "podman",
214+
"ansible_podman_executable": f"{self.podman_exec}",
215+
}
216+
217+
def sanity_checks(self):
218+
"""Implement Podman driver sanity checks."""
219+
if self._sanity_passed:
220+
return
221+
222+
log.info("Sanity checks: '%s'", self._name)
223+
# TODO(ssbarnea): reuse ansible runtime instance from molecule once it
224+
# fully adopts ansible-compat
225+
runtime = Runtime()
226+
if runtime.version < Version("2.10.0"):
227+
228+
if runtime.config.ansible_pipelining:
229+
sysexit_with_message(
230+
"Podman connections do not work with Ansible "
231+
f"{runtime.version} when pipelining is enabled. "
232+
"Disable pipelining or "
233+
"upgrade Ansible to 2.11 or newer.",
234+
code=RC_SETUP_ERROR,
235+
)
236+
warnings.warn(
237+
f"Use of molecule-podman with Ansible {runtime.version} is "
238+
"unsupported, upgrade to Ansible 2.11 or newer. "
239+
"Do not raise any bugs if your tests are failing with current configuration.",
240+
category=MoleculeRuntimeWarning,
241+
)
242+
self._sanity_passed = True
243+
244+
@property
245+
def required_collections(self) -> Dict[str, str]:
246+
"""Return collections dict containing names and versions required."""
247+
return {"containers.podman": "1.7.0", "ansible.posix": "1.3.0"}

0 commit comments

Comments
 (0)