Skip to content

Commit

Permalink
Assimilate docker plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
ssbarnea committed Jan 5, 2023
1 parent 44da846 commit 0a54112
Show file tree
Hide file tree
Showing 25 changed files with 852 additions and 1 deletion.
2 changes: 1 addition & 1 deletion .github/workflows/tox.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ jobs:
runs-on: ubuntu-22.04
needs: pre
env:
PYTEST_REQPASS: 3
PYTEST_REQPASS: 4
strategy:
fail-fast: false
matrix: ${{ fromJson(needs.pre.outputs.matrix) }}
Expand Down
11 changes: 11 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,22 @@ test = [
"molecule[test]"
]
azure = []
docker = [
# selinux python module is needed as least by ansible-docker modules
# and allows use of isolated (default) virtualenvs. It does not avoid need
# to install the system selinux libraries but it will provide a clear
# message when user has to do that.
'selinux; sys_platform=="linux2"',
'selinux; sys_platform=="linux"',
"docker >= 4.3.1",
"requests" # also required by docker
]
gce = []


[project.entry-points."molecule.driver"]
azure = "molecule_plugins.azure.driver:Azure"
docker = "molecule_plugins.docker.driver:Docker"
gce = "molecule_plugins.gce.driver:GCE"

[tool.setuptools_scm]
Expand Down
4 changes: 4 additions & 0 deletions requirements.yml
Original file line number Diff line number Diff line change
@@ -1,2 +1,6 @@
collections:
- name: google.cloud
- name: community.docker
version: ">=3.0.2"
- name: ansible.posix # docker
version: ">=1.4.0"
1 change: 1 addition & 0 deletions src/molecule_plugins/docker/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"""Molecule Docker Driver."""
5 changes: 5 additions & 0 deletions src/molecule_plugins/docker/cookiecutter/cookiecutter.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"molecule_directory": "molecule",
"role_name": "OVERRIDDEN",
"scenario_name": "OVERRIDDEN"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
- name: Converge
hosts: all
tasks:
- name: "Include {{ cookiecutter.role_name }}"
include_role:
name: "{{ cookiecutter.role_name }}"
275 changes: 275 additions & 0 deletions src/molecule_plugins/docker/driver.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,275 @@
# Copyright (c) 2015-2018 Cisco Systems, Inc.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to
# deal in the Software without restriction, including without limitation the
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
# sell copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
# DEALINGS IN THE SOFTWARE.
"""Docker Driver Module."""

from __future__ import absolute_import

import os
from typing import Dict

from molecule import logger
from molecule.api import Driver
from molecule.util import sysexit_with_message

log = logger.get_logger(__name__)


class Docker(Driver):
"""
Docker Driver Class.
The class responsible for managing `Docker`_ containers. `Docker`_ is
the default driver used in Molecule.
Molecule leverages Ansible's `docker_container`_ module, by mapping
variables from ``molecule.yml`` into ``create.yml`` and ``destroy.yml``.
Molecule leverages Ansible's `docker_network`_ module, by mapping variable
``docker_networks`` into ``create.yml`` and ``destroy.yml``.
.. _`docker_container`: https://docs.ansible.com/ansible/latest/modules/docker_container_module.html
.. _`docker_network`: https://docs.ansible.com/ansible/latest/modules/docker_network_module.html
.. _`Docker Security Configuration`: https://docs.docker.com/engine/reference/run/#security-configuration
.. _`Docker daemon socket options`: https://docs.docker.com/engine/reference/commandline/dockerd/#daemon-socket-option
.. code-block:: yaml
driver:
name: docker
platforms:
- name: instance
hostname: instance
image: image_name:tag
dockerfile: Dockerfile.j2
pull: True|False
pre_build_image: True|False
registry:
url: registry.example.com
credentials:
username: $USERNAME
password: $PASSWORD
email: [email protected]
user: root
override_command: True|False
command: sleep infinity
tty: True|False
pid_mode: host
privileged: True|False
security_opts:
- seccomp=unconfined
cgroupns_mode: host|private
devices:
- /dev/fuse:/dev/fuse:rwm
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:ro
keep_volumes: True|False
tmpfs:
- /tmp
- /run
capabilities:
- SYS_ADMIN
sysctls:
net.core.somaxconn: 1024
net.ipv4.tcp_syncookies: 0
exposed_ports:
- 53/udp
- 53/tcp
published_ports:
- 0.0.0.0:8053:53/udp
- 0.0.0.0:8053:53/tcp
ulimits:
- nofile:262144:262144
dns_servers:
- 8.8.8.8
etc_hosts: "{'host1.example.com': '10.3.1.5'}"
docker_networks:
- name: foo
ipam_config:
- subnet: '10.3.1.0/24'
gateway: '10.3.1.254'
networks:
- name: foo
- name: bar
network_mode: host
purge_networks: true
docker_host: tcp://localhost:12376
cacert_path: /foo/bar/ca.pem
cert_path: /foo/bar/cert.pem
key_path: /foo/bar/key.pem
tls_verify: true
env:
FOO: bar
restart_policy: on-failure
restart_retries: 1
buildargs:
http_proxy: http://proxy.example.com:8080/
If specifying the `CMD`_ directive in your ``Dockerfile.j2`` or consuming a
built image which declares a ``CMD`` directive, then you must set
``override_command: False``. Otherwise, Molecule takes care to honour the
value of the ``command`` key or uses the default of ``bash -c "while true;
do sleep 10000; done"`` to run the container until it is provisioned.
When attempting to utilize a container image with `systemd`_ as your init
system inside the container to simulate a real machine, make sure to set
the ``privileged``, ``volumes``, ``command``, and ``env``
values. An example using the ``centos:8`` image is below:
.. note:: Do note that running containers in privileged mode is considerably
less secure. For details, please reference `Docker Security
Configuration`_
.. note:: On some systems (macOS) you might have to set ``cgroupns_mode`` to
``host`` for `systemd` to work. See `Docker Desktop release
notes`_ for more information.
.. _`Docker Desktop release notes`: https://docs.docker.com/desktop/release-notes/#bug-fixes-and-minor-changes-19
.. note:: With the environment variable ``DOCKER_HOST`` the user can bind
Molecule to a different `Docker`_ socket than the default
``unix:///var/run/docker.sock``. ``tcp``, ``fd`` and ``ssh``
socket types can be configured. For details, please reference
`Docker daemon socket options`_.
.. code-block:: yaml
platforms:
- name: instance
image: quay.io/centos/centos:stream8
privileged: true
volumes:
- "/sys/fs/cgroup:/sys/fs/cgroup:rw"
command: "/usr/sbin/init"
tty: True
env:
container: docker
.. code-block:: bash
$ python3 -m pip install molecule[docker]
When pulling from a private registry, it is the user's discretion to decide
whether to use hard-code strings or environment variables for passing
credentials to molecule.
.. important::
Hard-coded credentials in ``molecule.yml`` should be avoided, instead use
`variable substitution`_.
Provide a list of files Molecule will preserve, relative to the scenario
ephemeral directory, after any ``destroy`` subcommand execution.
.. code-block:: yaml
driver:
name: docker
safe_files:
- foo
.. _`Docker`: https://www.docker.com
.. _`systemd`: https://www.freedesktop.org/wiki/Software/systemd/
.. _`CMD`: https://docs.docker.com/engine/reference/builder/#cmd
""" # noqa

_passed_sanity = False

def __init__(self, config=None):
"""Construct Docker."""
super(Docker, self).__init__(config)
self._name = "docker"

@property
def name(self):
return self._name

@name.setter
def name(self, value):
self._name = value

@property
def login_cmd_template(self):
return (
"docker exec "
"-e COLUMNS={columns} "
"-e LINES={lines} "
"-e TERM=bash "
"-e TERM=xterm "
"-ti {instance} bash"
)

@property
def default_safe_files(self):
return [os.path.join(self._config.scenario.ephemeral_directory, "Dockerfile")]

@property
def default_ssh_connection_options(self):
return []

def login_options(self, instance_name):
return {"instance": instance_name}

def ansible_connection_options(self, instance_name):
x = {"ansible_connection": "community.docker.docker"}
if "DOCKER_HOST" in os.environ:
x["ansible_docker_extra_args"] = f"-H={os.environ['DOCKER_HOST']}"
return x

def sanity_checks(self):
"""Implement Docker driver sanity checks."""
if self._passed_sanity:
return

log.info("Sanity checks: '%s'", self._name)
try:
import docker
import requests

docker_client = docker.from_env()
docker_client.ping()
except requests.exceptions.ConnectionError:
msg = (
"Unable to contact the Docker daemon. "
"Please refer to https://docs.docker.com/config/daemon/ "
"for managing the daemon"
)
sysexit_with_message(msg)

self._passed_sanity = True

def reset(self):
import docker

client = docker.from_env()
for c in client.containers.list(filters={"label": "owner=molecule"}):
log.info("Stopping docker container %s ...", c.id)
c.stop(timeout=3)
result = client.containers.prune(filters={"label": "owner=molecule"})
for c in result.get("ContainersDeleted") or []:
log.info("Deleted container %s", c)
for n in client.networks.list(filters={"label": "owner=molecule"}):
log.info("Removing docker network %s ...", n.name)
n.remove()

@property
def required_collections(self) -> Dict[str, str]:
"""Return collections dict containing names and versions required."""
# https://galaxy.ansible.com/community/docker
return {"community.docker": "3.0.2", "ansible.posix": "1.4.0"}
22 changes: 22 additions & 0 deletions src/molecule_plugins/docker/playbooks/Dockerfile.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Molecule managed

{% if item.registry is defined %}
FROM {{ item.registry.url }}/{{ item.image }}
{% else %}
FROM {{ item.image }}
{% endif %}

{% if item.env is defined %}
{% for var, value in item.env.items() %}
{% if value %}
ENV {{ var }} {{ value }}
{% endif %}
{% endfor %}
{% endif %}

RUN if [ $(command -v apt-get) ]; then export DEBIAN_FRONTEND=noninteractive && apt-get update && apt-get install -y python3 sudo bash ca-certificates iproute2 python3-apt aptitude && apt-get clean && rm -rf /var/lib/apt/lists/*; \
elif [ $(command -v dnf) ]; then dnf makecache && dnf --assumeyes install /usr/bin/python3 /usr/bin/python3-config /usr/bin/dnf-3 sudo bash iproute && dnf clean all; \
elif [ $(command -v yum) ]; then yum makecache fast && yum install -y /usr/bin/python /usr/bin/python2-config sudo yum-plugin-ovl bash iproute && sed -i 's/plugins=0/plugins=1/g' /etc/yum.conf && yum clean all; \
elif [ $(command -v zypper) ]; then zypper refresh && zypper install -y python3 sudo bash iproute2 && zypper clean -a; \
elif [ $(command -v apk) ]; then apk update && apk add --no-cache python3 sudo bash ca-certificates; \
elif [ $(command -v xbps-install) ]; then xbps-install -Syu && xbps-install -y python3 sudo bash ca-certificates iproute2 && xbps-remove -O; fi
Loading

0 comments on commit 0a54112

Please sign in to comment.