Skip to content

Commit

Permalink
Add custom create.yml with platform support
Browse files Browse the repository at this point in the history
Can remove custom create.yml, Dockerfile.j2, filter_plugins,
create_network.yml, delete_network.yml once upstream has merged
ansible-community/molecule-docker#122.
  • Loading branch information
CorbanR committed Dec 5, 2021
1 parent d8a332e commit 0fbab6e
Show file tree
Hide file tree
Showing 7 changed files with 287 additions and 3 deletions.
8 changes: 5 additions & 3 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,11 @@
if [ $(uname -s) == "Darwin" ]; then
export DOCKER_DEFAULT_PLATFORM=linux/amd64
# This is to override the default images used by molecule(ansible testing)
export CENTOS_EIGHT_IMAGE=centos:centos8@sha256:a1801b843b1bfaf77c501e7a6d3f709401a1e0c83863037fa3aab063a7fdb9dc
export UBUNTU_FOCAL_IMAGE=ubuntu:20.04@sha256:7cc0576c7c0ec2384de5cbf245f41567e922aab1b075f3e8ad565f508032df17
export UBUNTU_BIONIC_IMAGE=ubuntu:18.04@sha256:fc0d6af5ab38dab33aa53643c4c4b312c6cd1f044c1a2229b2743b252b9689fc
# These were required as upstream molecule is defaulting to system image (on my m1 that is arm64). Added custom create.yml
# with platform option. This is needed until https://github.com/ansible-community/molecule-docker/pull/122 is merged.
#export centos_eight_image=centos:centos8@sha256:a1801b843b1bfaf77c501e7a6d3f709401a1e0c83863037fa3aab063a7fdb9dc
#export UBUNTU_FOCAL_IMAGE=ubuntu:20.04@sha256:7cc0576c7c0ec2384de5cbf245f41567e922aab1b075f3e8ad565f508032df17
#export UBUNTU_BIONIC_IMAGE=ubuntu:18.04@sha256:fc0d6af5ab38dab33aa53643c4c4b312c6cd1f044c1a2229b2743b252b9689fc
fi
# For python virtualenv
SOURCE_DATE_EPOCH=$(date +%s)
Expand Down
22 changes: 22 additions & 0 deletions molecule/default/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
170 changes: 170 additions & 0 deletions molecule/default/create.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
---
- name: Create
hosts: localhost
connection: local
gather_facts: false
no_log: "{{ molecule_no_log }}"
vars:
molecule_labels:
owner: molecule
tasks:
- name: Log into a Docker registry
community.docker.docker_login:
username: "{{ item.registry.credentials.username }}"
password: "{{ item.registry.credentials.password }}"
email: "{{ item.registry.credentials.email | default(omit) }}"
registry: "{{ item.registry.url }}"
docker_host: "{{ item.docker_host | default(lookup('env', 'DOCKER_HOST') or 'unix://var/run/docker.sock') }}"
cacert_path: "{{ item.cacert_path | default((lookup('env', 'DOCKER_CERT_PATH') + '/ca.pem') if lookup('env', 'DOCKER_CERT_PATH') else omit) }}"
cert_path: "{{ item.cert_path | default((lookup('env', 'DOCKER_CERT_PATH') + '/cert.pem') if lookup('env', 'DOCKER_CERT_PATH') else omit) }}"
key_path: "{{ item.key_path | default((lookup('env', 'DOCKER_CERT_PATH') + '/key.pem') if lookup('env', 'DOCKER_CERT_PATH') else omit) }}"
tls_verify: "{{ item.tls_verify | default(lookup('env', 'DOCKER_TLS_VERIFY')) or false }}"
with_items: "{{ molecule_yml.platforms }}"
when:
- item.registry is defined
- item.registry.credentials is defined
- item.registry.credentials.username is defined
no_log: true

- name: Check presence of custom Dockerfiles
ansible.builtin.stat:
path: "{{ molecule_scenario_directory + '/' + (item.dockerfile | default( 'Dockerfile.j2')) }}"
loop: "{{ molecule_yml.platforms }}"
register: dockerfile_stats

- name: Create Dockerfiles from image names
ansible.builtin.template:
# when using embedded playbooks the dockerfile is alonside them
src: >-
{%- if dockerfile_stats.results[i].stat.exists -%}
{{ molecule_scenario_directory + '/' + (item.dockerfile | default( 'Dockerfile.j2')) }}
{%- else -%}
{{ playbook_dir + '/Dockerfile.j2' }}
{%- endif -%}
dest: "{{ molecule_ephemeral_directory }}/Dockerfile_{{ item.image | regex_replace('[^a-zA-Z0-9_]', '_') }}"
mode: "0600"
loop: "{{ molecule_yml.platforms }}"
loop_control:
index_var: i
when: not item.pre_build_image | default(false)
register: platforms

- name: Discover local Docker images
community.docker.docker_image_info:
name: "molecule_local/{{ item.item.name }}"
docker_host: "{{ item.item.docker_host | default(lookup('env', 'DOCKER_HOST') or 'unix://var/run/docker.sock') }}"
cacert_path: "{{ item.cacert_path | default((lookup('env', 'DOCKER_CERT_PATH') + '/ca.pem') if lookup('env', 'DOCKER_CERT_PATH') else omit) }}"
cert_path: "{{ item.cert_path | default((lookup('env', 'DOCKER_CERT_PATH') + '/cert.pem') if lookup('env', 'DOCKER_CERT_PATH') else omit) }}"
key_path: "{{ item.key_path | default((lookup('env', 'DOCKER_CERT_PATH') + '/key.pem') if lookup('env', 'DOCKER_CERT_PATH') else omit) }}"
tls_verify: "{{ item.tls_verify | default(lookup('env', 'DOCKER_TLS_VERIFY')) or false }}"
with_items: "{{ platforms.results }}"
when:
- not item.pre_build_image | default(false)
register: docker_images

- name: Build an Ansible compatible image (new) # noqa: no-handler
when:
- platforms.changed or docker_images.results | map(attribute='images') | select('equalto', []) | list | count >= 0
- not item.item.pre_build_image | default(false)
community.docker.docker_image:
build:
path: "{{ molecule_ephemeral_directory }}"
dockerfile: "{{ item.invocation.module_args.dest }}"
pull: "{{ item.item.pull | default(true) }}"
network: "{{ item.item.network_mode | default(omit) }}"
args: "{{ item.item.buildargs | default(omit) }}"
platform: "{{ item.item.platform | default(omit) }}"
name: "molecule_local/{{ item.item.image }}"
docker_host: "{{ item.item.docker_host | default(lookup('env', 'DOCKER_HOST') or 'unix://var/run/docker.sock') }}"
cacert_path: "{{ item.cacert_path | default((lookup('env', 'DOCKER_CERT_PATH') + '/ca.pem') if lookup('env', 'DOCKER_CERT_PATH') else omit) }}"
cert_path: "{{ item.cert_path | default((lookup('env', 'DOCKER_CERT_PATH') + '/cert.pem') if lookup('env', 'DOCKER_CERT_PATH') else omit) }}"
key_path: "{{ item.key_path | default((lookup('env', 'DOCKER_CERT_PATH') + '/key.pem') if lookup('env', 'DOCKER_CERT_PATH') else omit) }}"
tls_verify: "{{ item.tls_verify | default(lookup('env', 'DOCKER_TLS_VERIFY')) or false }}"
force_source: "{{ item.item.force | default(true) }}"
source: build
with_items: "{{ platforms.results }}"
loop_control:
label: "molecule_local/{{ item.item.image }}"
no_log: false
register: result
until: result is not failed
retries: 3
delay: 30

- name: Create docker network(s)
ansible.builtin.include_tasks: tasks/create_network.yml
with_items: "{{ molecule_yml.platforms | molecule_get_docker_networks(molecule_labels) }}"
loop_control:
label: "{{ item.name }}"
no_log: false

- name: Determine the CMD directives
ansible.builtin.set_fact:
command_directives_dict: >-
{{ command_directives_dict | default({}) |
combine({ item.name: item.command | default('bash -c "while true; do sleep 10000; done"') })
}}
with_items: "{{ molecule_yml.platforms }}"
when: item.override_command | default(true)

- name: Create molecule instance(s)
community.docker.docker_container:
name: "{{ item.name }}"
docker_host: "{{ item.docker_host | default(lookup('env', 'DOCKER_HOST') or 'unix://var/run/docker.sock') }}"
cacert_path: "{{ item.cacert_path | default((lookup('env', 'DOCKER_CERT_PATH') + '/ca.pem') if lookup('env', 'DOCKER_CERT_PATH') else omit) }}"
cert_path: "{{ item.cert_path | default((lookup('env', 'DOCKER_CERT_PATH') + '/cert.pem') if lookup('env', 'DOCKER_CERT_PATH') else omit) }}"
key_path: "{{ item.key_path | default((lookup('env', 'DOCKER_CERT_PATH') + '/key.pem') if lookup('env', 'DOCKER_CERT_PATH') else omit) }}"
tls_verify: "{{ item.tls_verify | default(lookup('env', 'DOCKER_TLS_VERIFY')) or false }}"
hostname: "{{ item.hostname | default(item.name) }}"
image: "{{ item.pre_build_image | default(false) | ternary('', 'molecule_local/') }}{{ item.image }}"
pull: "{{ item.pull | default(omit) }}"
memory: "{{ item.memory | default(omit) }}"
memory_swap: "{{ item.memory_swap | default(omit) }}"
state: started
recreate: false
log_driver: json-file
command: "{{ (command_directives_dict | default({}))[item.name] | default(omit) }}"
command_handling: "{{ item.command_handling | default('compatibility') }}"
user: "{{ item.user | default(omit) }}"
pid_mode: "{{ item.pid_mode | default(omit) }}"
privileged: "{{ item.privileged | default(omit) }}"
security_opts: "{{ item.security_opts | default(omit) }}"
devices: "{{ item.devices | default(omit) }}"
links: "{{ item.links | default(omit) }}"
volumes: "{{ item.volumes | default(omit) }}"
mounts: "{{ item.mounts | default(omit) }}"
tmpfs: "{{ item.tmpfs | default(omit) }}"
capabilities: "{{ item.capabilities | default(omit) }}"
sysctls: "{{ item.sysctls | default(omit) }}"
exposed_ports: "{{ item.exposed_ports | default(omit) }}"
published_ports: "{{ item.published_ports | default(omit) }}"
ulimits: "{{ item.ulimits | default(omit) }}"
networks: "{{ item.networks | default(omit) }}"
network_mode: "{{ item.network_mode | default(omit) }}"
networks_cli_compatible: "{{ item.networks_cli_compatible | default(true) }}"
purge_networks: "{{ item.purge_networks | default(omit) }}"
dns_servers: "{{ item.dns_servers | default(omit) }}"
etc_hosts: "{{ item.etc_hosts | default(omit) }}"
env: "{{ item.env | default(omit) }}"
restart_policy: "{{ item.restart_policy | default(omit) }}"
restart_retries: "{{ item.restart_retries | default(omit) }}"
tty: "{{ item.tty | default(omit) }}"
labels: "{{ molecule_labels | combine(item.labels | default({})) }}"
container_default_behavior: "{{ item.container_default_behavior | default('compatibility' if ansible_version.full is version_compare('2.10', '>=') else omit) }}"
stop_signal: "{{ item.stop_signal | default(omit) }}"
kill_signal: "{{ item.kill_signal | default(omit) }}"
register: server
with_items: "{{ molecule_yml.platforms }}"
loop_control:
label: "{{ item.name }}"
no_log: false
async: 7200
poll: 0

- name: Wait for instance(s) creation to complete
ansible.builtin.async_status:
jid: "{{ item.ansible_job_id }}"
register: docker_jobs
until: docker_jobs.finished
retries: 300
with_items: "{{ server.results }}"
37 changes: 37 additions & 0 deletions molecule/default/filter_plugins/get_docker_networks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""Embedded ansible filter used by Molecule Docker driver create playbook."""


def get_docker_networks(data, labels={}):
"""Get list of docker networks."""
network_list = []
network_names = []
for platform in data:
if "docker_networks" in platform:
for docker_network in platform["docker_networks"]:
if "labels" not in docker_network:
docker_network["labels"] = {}
for key in labels:
docker_network["labels"][key] = labels[key]

if "name" in docker_network:
network_list.append(docker_network)
network_names.append(docker_network["name"])

# If a network name is defined for a platform but is not defined in
# docker_networks, add it to the network list.
if "networks" in platform:
for network in platform["networks"]:
if "name" in network:
name = network["name"]
if name not in network_names:
network_list.append({"name": name, "labels": labels})
return network_list


class FilterModule(object):
"""Core Molecule filter plugins."""

def filters(self):
return {
"molecule_get_docker_networks": get_docker_networks,
}
5 changes: 5 additions & 0 deletions molecule/default/molecule.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,19 @@ dependency:
driver:
name: docker
platforms:
# linux/arm64/v8 is for apple m1, crystal requires building from source for m1 support
- name: centos8
image: ${CENTOS_EIGHT_IMAGE:-centos:centos8}
platform: ${DOCKER_DEFAULT_PLATFORM:-linux/amd64}
- name: centos7
image: ${CENTOS_SEVEN_IMAGE:-centos:centos7}
platform: ${DOCKER_DEFAULT_PLATFORM:-linux/amd64}
- name: ubuntu1804
image: ${UBUNTU_BIONIC_IMAGE:-ubuntu:18.04}
platform: ${DOCKER_DEFAULT_PLATFORM:-linux/amd64}
- name: ubuntu2004
image: ${UBUNTU_FOCAL_IMAGE:-ubuntu:20.04}
platform: ${DOCKER_DEFAULT_PLATFORM:-linux/amd64}
provisioner:
name: ansible
scenario:
Expand Down
35 changes: 35 additions & 0 deletions molecule/default/tasks/create_network.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
- name: Check if network exist
community.docker.docker_network_info:
name: "{{ item.name }}"
register: docker_netname

- name: Create docker network(s)
community.docker.docker_network:
api_version: "{{ item.api_version | default(omit) }}"
appends: "{{ item.appends | default(omit) }}"
attachable: "{{ item.attachable | default(omit) }}"
ca_cert: "{{ item.ca_cert | default(omit) }}"
client_cert: "{{ item.client_cert | default(omit) }}"
client_key: "{{ item.client_key | default(omit) }}"
connected: "{{ item.connected | default(omit) }}"
debug: "{{ item.debug | default(omit) }}"
docker_host: "{{ item.docker_host | default(omit) }}"
driver: "{{ item.driver | default(omit) }}"
driver_options: "{{ item.driver_options | default(omit) }}"
enable_ipv6: "{{ item.enable_ipv6 | default(omit) }}"
force: "{{ item.force | default(omit) }}"
internal: "{{ item.internal | default(omit) }}"
ipam_config: "{{ item.ipam_config | default(omit) }}"
ipam_driver: "{{ item.ipam_driver | default(omit) }}"
ipam_driver_options: "{{ item.ipam_driver_options | default(omit) }}"
key_path: "{{ item.key_path | default(omit) }}"
labels: "{{ item.labels }}"
name: "{{ item.name }}"
scope: "{{ item.scope | default(omit) }}"
ssl_version: "{{ item.ssl_version | default(omit) }}"
state: "present"
timeout: "{{ item.timeout | default(omit) }}"
tls: "{{ item.tls | default(omit) }}"
tls_hostname: "{{ item.tls_hostname | default(omit) }}"
validate_certs: "{{ item.validate_certs | default(omit) }}"
when: not docker_netname.exists
13 changes: 13 additions & 0 deletions molecule/default/tasks/delete_network.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
- name: Retrieve network info
community.docker.docker_network_info:
name: "{{ item.name }}"
register: docker_netname

- name: Delete docker network(s)
community.docker.docker_network:
name: "{{ item.name }}"
state: "absent"
when:
- docker_netname.exists
- docker_netname.network.Labels.owner is defined
- docker_netname.network.Labels.owner == 'molecule'

0 comments on commit 0fbab6e

Please sign in to comment.