1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2024-09-14 20:13:21 +02:00

Removal of docker content for 2.0.0 (#1304)

* Remove docker content.

* Clean up BOTMETA.

* Add redirects, tombstone docker_image_facts and docker_service.

* Add changelog.

* Remove docker action group.

* Rewrite fragment.

* Ansible 2.11 is now called Ansible 3.0.0.
This commit is contained in:
Felix Fontein 2020-11-23 13:03:19 +01:00 committed by GitHub
parent a96f90ff94
commit f896c2986c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
261 changed files with 78 additions and 37392 deletions

54
.github/BOTMETA.yml vendored
View file

@ -31,11 +31,6 @@ files:
$callbacks/unixy.py:
maintainers: akatch
labels: unixy
$connections/docker.py:
maintainers: $team_docker
labels: cloud docker
ignore: cove
supershipit: felixfontein
$connections/:
labels: connections
$connections/kubectl.py:
@ -51,11 +46,6 @@ files:
labels: saltstack
$doc_fragments/:
labels: docs_fragments
$doc_fragments/docker.py:
maintainers: $team_docker
labels: cloud docker
ignore: cove
supershipit: felixfontein
$doc_fragments/gcp.py:
maintainers: $team_google
labels: gcp
@ -90,16 +80,6 @@ files:
keywords: firepower ftd
$inventories/:
labels: inventories
$inventories/docker_machine.py:
maintainers: $team_docker
labels: cloud docker
ignore: cove
supershipit: felixfontein
$inventories/docker_swarm.py:
maintainers: $team_docker morph027
labels: cloud docker docker_swarm
ignore: cove
supershipit: felixfontein
$inventories/linode.py:
maintainers: $team_linode
labels: cloud linode
@ -137,11 +117,6 @@ files:
labels: infoblox networking
$module_utils/:
labels: module_utils
$module_utils/docker/:
maintainers: $team_docker
labels: cloud
ignore: cove
supershipit: felixfontein
$module_utils/gitlab.py:
notify: jlozadad
maintainers: $team_gitlab
@ -210,34 +185,6 @@ files:
labels: dimensiondata_network
$modules/cloud/dimensiondata/dimensiondata_vlan.py:
maintainers: tintoy
$modules/cloud/docker/:
maintainers: $team_docker
ignore: cove
supershipit: felixfontein
$modules/cloud/docker/docker_compose.py:
maintainers: sluther
labels: docker_compose
$modules/cloud/docker/docker_config.py:
maintainers: ushuz
$modules/cloud/docker/docker_container.py:
maintainers: dusdanig softzilla zfil
ignore: ThomasSteinbach cove joshuaconner
$modules/cloud/docker/docker_image.py:
maintainers: softzilla ssbarnea
$modules/cloud/docker/docker_login.py:
maintainers: olsaki
$modules/cloud/docker/docker_network.py:
maintainers: keitwb
labels: docker_network
$modules/cloud/docker/docker_stack_task_info.py:
maintainers: imjoseangel
$modules/cloud/docker/docker_swarm_service.py:
maintainers: hannseman
labels: docker_swarm_service
$modules/cloud/docker/docker_swarm_service_info.py:
maintainers: hannseman
$modules/cloud/docker/docker_volume.py:
maintainers: agronholm
$modules/cloud/google/:
maintainers: $team_google
ignore: supertom
@ -1094,7 +1041,6 @@ macros:
team_bsd: JoergFiedler MacLemon bcoca dch jasperla mekanix opoplawski overhacked tuxillo
team_consul: colin-nolan sgargan
team_cyberark_conjur: jvanderhoof ryanprior
team_docker: DBendit WojciechowskiPiotr akshay196 danihodovic dariko felixfontein jwitko kassiansun tbouvet chouseknecht
team_e_spirit: MatrixCrawler getjack
team_flatpak: JayKayy oolongbrothers
team_gitlab: Lunik Shaps dj-wasabi marwatk waheedi zanssa scodeman

View file

@ -0,0 +1,14 @@
removed_features:
- >
All ``docker`` modules and plugins have been removed from this collection.
They have been migrated to the `community.docker <https://galaxy.ansible.com/community/docker>`_ collection.
If you use ansible-base 2.10 or newer, redirections have been provided.
If you use Ansible 2.9 and installed this collection, you need to adjust the FQCNs (``community.general.docker_container`` → ``community.docker.docker_container``) and make sure to install the community.docker collection.
breaking_changes:
- >
If you use Ansible 2.9 and the ``docker`` plugins or modules from this collections, community.general 2.0.0 results in errors when trying to use the docker content by FQCN, like ``community.general.docker_container``.
Since Ansible 2.9 is not able to use redirections, you will have to adjust your playbooks and roles manually to use the new FQCNs (``community.docker.docker_container`` for the previous example) and to make sure that you have ``community.docker`` installed.
If you use ansible-base 2.10 or newer and did not install Ansible 3.0.0, but installed (and/or upgraded) community.general manually, you need to make sure to also install ``community.docker`` if you are using any of the ``docker`` plugins or modules.
While ansible-base 2.10 or newer can use the redirects that community.general 2.0.0 adds, the collection they point to (community.docker) must be installed for them to work.

View file

@ -1,29 +1,5 @@
requires_ansible: '>=2.9.10'
action_groups:
docker:
- docker_swarm
- docker_image_facts
- docker_service
- docker_compose
- docker_config
- docker_container
- docker_container_info
- docker_host_info
- docker_image
- docker_image_info
- docker_login
- docker_network
- docker_network_info
- docker_node
- docker_node_info
- docker_prune
- docker_secret
- docker_swarm
- docker_swarm_info
- docker_swarm_service
- docker_swarm_service_info
- docker_volume
- docker_volume_info
k8s:
- kubevirt_cdi_upload
- kubevirt_preset
@ -57,6 +33,9 @@ action_groups:
- ovirt_vm_facts
- ovirt_vmpool_facts
plugin_routing:
connection:
docker:
redirect: community.docker.docker
lookup:
conjur_variable:
redirect: cyberark.conjur.conjur_variable
@ -238,14 +217,60 @@ plugin_routing:
removal_version: 2.0.0
warning_text: The digital_ocean_volume_info module has been moved to the community.digitalocean collection.
redirect: community.digitalocean.digital_ocean_volume_info
docker_compose:
redirect: community.docker.docker_compose
docker_config:
redirect: community.docker.docker_config
docker_container:
redirect: community.docker.docker_container
docker_container_info:
redirect: community.docker.docker_container_info
docker_host_info:
redirect: community.docker.docker_host_info
docker_image:
redirect: community.docker.docker_image
docker_image_facts:
deprecation:
tombstone:
removal_version: 2.0.0
warning_text: see plugin documentation for details
warning_text: Use community.docker.docker_image_info instead.
docker_image_info:
redirect: community.docker.docker_image_info
docker_login:
redirect: community.docker.docker_login
docker_network:
redirect: community.docker.docker_network
docker_network_info:
redirect: community.docker.docker_network_info
docker_node:
redirect: community.docker.docker_node
docker_node_info:
redirect: community.docker.docker_node_info
docker_prune:
redirect: community.docker.docker_prune
docker_secret:
redirect: community.docker.docker_secret
docker_service:
deprecation:
tombstone:
removal_version: 2.0.0
warning_text: see plugin documentation for details
warning_text: Use community.docker.docker_compose instead.
docker_stack:
redirect: community.docker.docker_stack
docker_stack_info:
redirect: community.docker.docker_stack_info
docker_stack_task_info:
redirect: community.docker.docker_stack_task_info
docker_swarm:
redirect: community.docker.docker_swarm
docker_swarm_info:
redirect: community.docker.docker_swarm_info
docker_swarm_service:
redirect: community.docker.docker_swarm_service
docker_swarm_service_info:
redirect: community.docker.docker_swarm_service_info
docker_volume:
redirect: community.docker.docker_volume
docker_volume_info:
redirect: community.docker.docker_volume_info
firewalld:
deprecation:
removal_version: 2.0.0
@ -704,6 +729,8 @@ plugin_routing:
removal_version: 2.0.0
warning_text: The digital_ocean docs_fragment has been moved to the community.digitalocean collection.
redirect: community.digitalocean.digital_ocean
docker:
redirect: community.docker.docker
infinibox:
redirect: infinidat.infinibox.infinibox
deprecation:
@ -725,6 +752,10 @@ plugin_routing:
removal_version: 2.0.0
warning_text: The digital_ocean module_utils has been moved to the community.digitalocean collection.
redirect: community.digitalocean.digital_ocean
docker.common:
redirect: community.docker.common
docker.swarm:
redirect: community.docker.swarm
firewalld:
deprecation:
removal_version: 2.0.0
@ -753,3 +784,8 @@ plugin_routing:
deprecation:
removal_version: 2.0.0
warning_text: see plugin documentation for details
inventory:
docker_machine:
redirect: community.docker.docker_machine
docker_swarm:
redirect: community.docker.docker_swarm

View file

@ -1,364 +0,0 @@
# Based on the chroot connection plugin by Maykel Moya
#
# (c) 2014, Lorin Hochstein
# (c) 2015, Leendert Brouwer (https://github.com/objectified)
# (c) 2015, Toshio Kuratomi <tkuratomi@ansible.com>
# Copyright (c) 2017 Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = '''
author:
- Lorin Hochestein (!UNKNOWN)
- Leendert Brouwer (!UNKNOWN)
connection: docker
short_description: Run tasks in docker containers
description:
- Run commands or put/fetch files to an existing docker container.
options:
remote_user:
description:
- The user to execute as inside the container
vars:
- name: ansible_user
- name: ansible_docker_user
docker_extra_args:
description:
- Extra arguments to pass to the docker command line
default: ''
remote_addr:
description:
- The name of the container you want to access.
default: inventory_hostname
vars:
- name: ansible_host
- name: ansible_docker_host
'''
import distutils.spawn
import fcntl
import os
import os.path
import subprocess
import re
from distutils.version import LooseVersion
import ansible.constants as C
from ansible.compat import selectors
from ansible.errors import AnsibleError, AnsibleFileNotFound
from ansible.module_utils.six.moves import shlex_quote
from ansible.module_utils._text import to_bytes, to_native, to_text
from ansible.plugins.connection import ConnectionBase, BUFSIZE
from ansible.utils.display import Display
display = Display()
class Connection(ConnectionBase):
''' Local docker based connections '''
transport = 'community.general.docker'
has_pipelining = True
def __init__(self, play_context, new_stdin, *args, **kwargs):
super(Connection, self).__init__(play_context, new_stdin, *args, **kwargs)
# Note: docker supports running as non-root in some configurations.
# (For instance, setting the UNIX socket file to be readable and
# writable by a specific UNIX group and then putting users into that
# group). Therefore we don't check that the user is root when using
# this connection. But if the user is getting a permission denied
# error it probably means that docker on their system is only
# configured to be connected to by root and they are not running as
# root.
# Windows uses Powershell modules
if getattr(self._shell, "_IS_WINDOWS", False):
self.module_implementation_preferences = ('.ps1', '.exe', '')
if 'docker_command' in kwargs:
self.docker_cmd = kwargs['docker_command']
else:
self.docker_cmd = distutils.spawn.find_executable('docker')
if not self.docker_cmd:
raise AnsibleError("docker command not found in PATH")
docker_version = self._get_docker_version()
if docker_version == u'dev':
display.warning(u'Docker version number is "dev". Will assume latest version.')
if docker_version != u'dev' and LooseVersion(docker_version) < LooseVersion(u'1.3'):
raise AnsibleError('docker connection type requires docker 1.3 or higher')
# The remote user we will request from docker (if supported)
self.remote_user = None
# The actual user which will execute commands in docker (if known)
self.actual_user = None
if self._play_context.remote_user is not None:
if docker_version == u'dev' or LooseVersion(docker_version) >= LooseVersion(u'1.7'):
# Support for specifying the exec user was added in docker 1.7
self.remote_user = self._play_context.remote_user
self.actual_user = self.remote_user
else:
self.actual_user = self._get_docker_remote_user()
if self.actual_user != self._play_context.remote_user:
display.warning(u'docker {0} does not support remote_user, using container default: {1}'
.format(docker_version, self.actual_user or u'?'))
elif self._display.verbosity > 2:
# Since we're not setting the actual_user, look it up so we have it for logging later
# Only do this if display verbosity is high enough that we'll need the value
# This saves overhead from calling into docker when we don't need to
self.actual_user = self._get_docker_remote_user()
@staticmethod
def _sanitize_version(version):
return re.sub(u'[^0-9a-zA-Z.]', u'', version)
def _old_docker_version(self):
cmd_args = []
if self._play_context.docker_extra_args:
cmd_args += self._play_context.docker_extra_args.split(' ')
old_version_subcommand = ['version']
old_docker_cmd = [self.docker_cmd] + cmd_args + old_version_subcommand
p = subprocess.Popen(old_docker_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
cmd_output, err = p.communicate()
return old_docker_cmd, to_native(cmd_output), err, p.returncode
def _new_docker_version(self):
# no result yet, must be newer Docker version
cmd_args = []
if self._play_context.docker_extra_args:
cmd_args += self._play_context.docker_extra_args.split(' ')
new_version_subcommand = ['version', '--format', "'{{.Server.Version}}'"]
new_docker_cmd = [self.docker_cmd] + cmd_args + new_version_subcommand
p = subprocess.Popen(new_docker_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
cmd_output, err = p.communicate()
return new_docker_cmd, to_native(cmd_output), err, p.returncode
def _get_docker_version(self):
cmd, cmd_output, err, returncode = self._old_docker_version()
if returncode == 0:
for line in to_text(cmd_output, errors='surrogate_or_strict').split(u'\n'):
if line.startswith(u'Server version:'): # old docker versions
return self._sanitize_version(line.split()[2])
cmd, cmd_output, err, returncode = self._new_docker_version()
if returncode:
raise AnsibleError('Docker version check (%s) failed: %s' % (to_native(cmd), to_native(err)))
return self._sanitize_version(to_text(cmd_output, errors='surrogate_or_strict'))
def _get_docker_remote_user(self):
""" Get the default user configured in the docker container """
p = subprocess.Popen([self.docker_cmd, 'inspect', '--format', '{{.Config.User}}', self._play_context.remote_addr],
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = p.communicate()
out = to_text(out, errors='surrogate_or_strict')
if p.returncode != 0:
display.warning(u'unable to retrieve default user from docker container: %s %s' % (out, to_text(err)))
return None
# The default exec user is root, unless it was changed in the Dockerfile with USER
return out.strip() or u'root'
def _build_exec_cmd(self, cmd):
""" Build the local docker exec command to run cmd on remote_host
If remote_user is available and is supported by the docker
version we are using, it will be provided to docker exec.
"""
local_cmd = [self.docker_cmd]
if self._play_context.docker_extra_args:
local_cmd += self._play_context.docker_extra_args.split(' ')
local_cmd += [b'exec']
if self.remote_user is not None:
local_cmd += [b'-u', self.remote_user]
# -i is needed to keep stdin open which allows pipelining to work
local_cmd += [b'-i', self._play_context.remote_addr] + cmd
return local_cmd
def _connect(self, port=None):
""" Connect to the container. Nothing to do """
super(Connection, self)._connect()
if not self._connected:
display.vvv(u"ESTABLISH DOCKER CONNECTION FOR USER: {0}".format(
self.actual_user or u'?'), host=self._play_context.remote_addr
)
self._connected = True
def exec_command(self, cmd, in_data=None, sudoable=False):
""" Run a command on the docker host """
super(Connection, self).exec_command(cmd, in_data=in_data, sudoable=sudoable)
local_cmd = self._build_exec_cmd([self._play_context.executable, '-c', cmd])
display.vvv(u"EXEC {0}".format(to_text(local_cmd)), host=self._play_context.remote_addr)
display.debug("opening command with Popen()")
local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd]
p = subprocess.Popen(
local_cmd,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
display.debug("done running command with Popen()")
if self.become and self.become.expect_prompt() and sudoable:
fcntl.fcntl(p.stdout, fcntl.F_SETFL, fcntl.fcntl(p.stdout, fcntl.F_GETFL) | os.O_NONBLOCK)
fcntl.fcntl(p.stderr, fcntl.F_SETFL, fcntl.fcntl(p.stderr, fcntl.F_GETFL) | os.O_NONBLOCK)
selector = selectors.DefaultSelector()
selector.register(p.stdout, selectors.EVENT_READ)
selector.register(p.stderr, selectors.EVENT_READ)
become_output = b''
try:
while not self.become.check_success(become_output) and not self.become.check_password_prompt(become_output):
events = selector.select(self._play_context.timeout)
if not events:
stdout, stderr = p.communicate()
raise AnsibleError('timeout waiting for privilege escalation password prompt:\n' + to_native(become_output))
for key, event in events:
if key.fileobj == p.stdout:
chunk = p.stdout.read()
elif key.fileobj == p.stderr:
chunk = p.stderr.read()
if not chunk:
stdout, stderr = p.communicate()
raise AnsibleError('privilege output closed while waiting for password prompt:\n' + to_native(become_output))
become_output += chunk
finally:
selector.close()
if not self.become.check_success(become_output):
become_pass = self.become.get_option('become_pass', playcontext=self._play_context)
p.stdin.write(to_bytes(become_pass, errors='surrogate_or_strict') + b'\n')
fcntl.fcntl(p.stdout, fcntl.F_SETFL, fcntl.fcntl(p.stdout, fcntl.F_GETFL) & ~os.O_NONBLOCK)
fcntl.fcntl(p.stderr, fcntl.F_SETFL, fcntl.fcntl(p.stderr, fcntl.F_GETFL) & ~os.O_NONBLOCK)
display.debug("getting output with communicate()")
stdout, stderr = p.communicate(in_data)
display.debug("done communicating")
display.debug("done with docker.exec_command()")
return (p.returncode, stdout, stderr)
def _prefix_login_path(self, remote_path):
''' Make sure that we put files into a standard path
If a path is relative, then we need to choose where to put it.
ssh chooses $HOME but we aren't guaranteed that a home dir will
exist in any given chroot. So for now we're choosing "/" instead.
This also happens to be the former default.
Can revisit using $HOME instead if it's a problem
'''
if getattr(self._shell, "_IS_WINDOWS", False):
import ntpath
return ntpath.normpath(remote_path)
else:
if not remote_path.startswith(os.path.sep):
remote_path = os.path.join(os.path.sep, remote_path)
return os.path.normpath(remote_path)
def put_file(self, in_path, out_path):
""" Transfer a file from local to docker container """
super(Connection, self).put_file(in_path, out_path)
display.vvv("PUT %s TO %s" % (in_path, out_path), host=self._play_context.remote_addr)
out_path = self._prefix_login_path(out_path)
if not os.path.exists(to_bytes(in_path, errors='surrogate_or_strict')):
raise AnsibleFileNotFound(
"file or module does not exist: %s" % to_native(in_path))
out_path = shlex_quote(out_path)
# Older docker doesn't have native support for copying files into
# running containers, so we use docker exec to implement this
# Although docker version 1.8 and later provide support, the
# owner and group of the files are always set to root
with open(to_bytes(in_path, errors='surrogate_or_strict'), 'rb') as in_file:
if not os.fstat(in_file.fileno()).st_size:
count = ' count=0'
else:
count = ''
args = self._build_exec_cmd([self._play_context.executable, "-c", "dd of=%s bs=%s%s" % (out_path, BUFSIZE, count)])
args = [to_bytes(i, errors='surrogate_or_strict') for i in args]
try:
p = subprocess.Popen(args, stdin=in_file,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
except OSError:
raise AnsibleError("docker connection requires dd command in the container to put files")
stdout, stderr = p.communicate()
if p.returncode != 0:
raise AnsibleError("failed to transfer file %s to %s:\n%s\n%s" %
(to_native(in_path), to_native(out_path), to_native(stdout), to_native(stderr)))
def fetch_file(self, in_path, out_path):
""" Fetch a file from container to local. """
super(Connection, self).fetch_file(in_path, out_path)
display.vvv("FETCH %s TO %s" % (in_path, out_path), host=self._play_context.remote_addr)
in_path = self._prefix_login_path(in_path)
# out_path is the final file path, but docker takes a directory, not a
# file path
out_dir = os.path.dirname(out_path)
args = [self.docker_cmd, "cp", "%s:%s" % (self._play_context.remote_addr, in_path), out_dir]
args = [to_bytes(i, errors='surrogate_or_strict') for i in args]
p = subprocess.Popen(args, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
p.communicate()
if getattr(self._shell, "_IS_WINDOWS", False):
import ntpath
actual_out_path = ntpath.join(out_dir, ntpath.basename(in_path))
else:
actual_out_path = os.path.join(out_dir, os.path.basename(in_path))
if p.returncode != 0:
# Older docker doesn't have native support for fetching files command `cp`
# If `cp` fails, try to use `dd` instead
args = self._build_exec_cmd([self._play_context.executable, "-c", "dd if=%s bs=%s" % (in_path, BUFSIZE)])
args = [to_bytes(i, errors='surrogate_or_strict') for i in args]
with open(to_bytes(actual_out_path, errors='surrogate_or_strict'), 'wb') as out_file:
try:
p = subprocess.Popen(args, stdin=subprocess.PIPE,
stdout=out_file, stderr=subprocess.PIPE)
except OSError:
raise AnsibleError("docker connection requires dd command in the container to put files")
stdout, stderr = p.communicate()
if p.returncode != 0:
raise AnsibleError("failed to fetch file %s to %s:\n%s\n%s" % (in_path, out_path, stdout, stderr))
# Rename if needed
if actual_out_path != out_path:
os.rename(to_bytes(actual_out_path, errors='strict'), to_bytes(out_path, errors='strict'))
def close(self):
""" Terminate the connection. Nothing to do for Docker"""
super(Connection, self).close()
self._connected = False

View file

@ -1,136 +0,0 @@
# -*- coding: utf-8 -*-
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
class ModuleDocFragment(object):
# Docker doc fragment
DOCUMENTATION = r'''
options:
docker_host:
description:
- The URL or Unix socket path used to connect to the Docker API. To connect to a remote host, provide the
TCP connection string. For example, C(tcp://192.0.2.23:2376). If TLS is used to encrypt the connection,
the module will automatically replace C(tcp) in the connection URL with C(https).
- If the value is not specified in the task, the value of environment variable C(DOCKER_HOST) will be used
instead. If the environment variable is not set, the default value will be used.
type: str
default: unix://var/run/docker.sock
aliases: [ docker_url ]
tls_hostname:
description:
- When verifying the authenticity of the Docker Host server, provide the expected name of the server.
- If the value is not specified in the task, the value of environment variable C(DOCKER_TLS_HOSTNAME) will
be used instead. If the environment variable is not set, the default value will be used.
type: str
default: localhost
api_version:
description:
- The version of the Docker API running on the Docker Host.
- Defaults to the latest version of the API supported by Docker SDK for Python and the docker daemon.
- If the value is not specified in the task, the value of environment variable C(DOCKER_API_VERSION) will be
used instead. If the environment variable is not set, the default value will be used.
type: str
default: auto
aliases: [ docker_api_version ]
timeout:
description:
- The maximum amount of time in seconds to wait on a response from the API.
- If the value is not specified in the task, the value of environment variable C(DOCKER_TIMEOUT) will be used
instead. If the environment variable is not set, the default value will be used.
type: int
default: 60
ca_cert:
description:
- Use a CA certificate when performing server verification by providing the path to a CA certificate file.
- If the value is not specified in the task and the environment variable C(DOCKER_CERT_PATH) is set,
the file C(ca.pem) from the directory specified in the environment variable C(DOCKER_CERT_PATH) will be used.
type: path
aliases: [ tls_ca_cert, cacert_path ]
client_cert:
description:
- Path to the client's TLS certificate file.
- If the value is not specified in the task and the environment variable C(DOCKER_CERT_PATH) is set,
the file C(cert.pem) from the directory specified in the environment variable C(DOCKER_CERT_PATH) will be used.
type: path
aliases: [ tls_client_cert, cert_path ]
client_key:
description:
- Path to the client's TLS key file.
- If the value is not specified in the task and the environment variable C(DOCKER_CERT_PATH) is set,
the file C(key.pem) from the directory specified in the environment variable C(DOCKER_CERT_PATH) will be used.
type: path
aliases: [ tls_client_key, key_path ]
ssl_version:
description:
- Provide a valid SSL version number. Default value determined by ssl.py module.
- If the value is not specified in the task, the value of environment variable C(DOCKER_SSL_VERSION) will be
used instead.
type: str
tls:
description:
- Secure the connection to the API by using TLS without verifying the authenticity of the Docker host
server. Note that if I(validate_certs) is set to C(yes) as well, it will take precedence.
- If the value is not specified in the task, the value of environment variable C(DOCKER_TLS) will be used
instead. If the environment variable is not set, the default value will be used.
type: bool
default: no
validate_certs:
description:
- Secure the connection to the API by using TLS and verifying the authenticity of the Docker host server.
- If the value is not specified in the task, the value of environment variable C(DOCKER_TLS_VERIFY) will be
used instead. If the environment variable is not set, the default value will be used.
type: bool
default: no
aliases: [ tls_verify ]
debug:
description:
- Debug mode
type: bool
default: no
notes:
- Connect to the Docker daemon by providing parameters with each task or by defining environment variables.
You can define C(DOCKER_HOST), C(DOCKER_TLS_HOSTNAME), C(DOCKER_API_VERSION), C(DOCKER_CERT_PATH), C(DOCKER_SSL_VERSION),
C(DOCKER_TLS), C(DOCKER_TLS_VERIFY) and C(DOCKER_TIMEOUT). If you are using docker machine, run the script shipped
with the product that sets up the environment. It will set these variables for you. See
U(https://docs.docker.com/machine/reference/env/) for more details.
- When connecting to Docker daemon with TLS, you might need to install additional Python packages.
For the Docker SDK for Python, version 2.4 or newer, this can be done by installing C(docker[tls]) with M(ansible.builtin.pip).
- Note that the Docker SDK for Python only allows to specify the path to the Docker configuration for very few functions.
In general, it will use C($HOME/.docker/config.json) if the C(DOCKER_CONFIG) environment variable is not specified,
and use C($DOCKER_CONFIG/config.json) otherwise.
'''
# Additional, more specific stuff for minimal Docker SDK for Python version < 2.0
DOCKER_PY_1_DOCUMENTATION = r'''
options: {}
requirements:
- "Docker SDK for Python: Please note that the L(docker-py,https://pypi.org/project/docker-py/)
Python module has been superseded by L(docker,https://pypi.org/project/docker/)
(see L(here,https://github.com/docker/docker-py/issues/1310) for details).
For Python 2.6, C(docker-py) must be used. Otherwise, it is recommended to
install the C(docker) Python module. Note that both modules should *not*
be installed at the same time. Also note that when both modules are installed
and one of them is uninstalled, the other might no longer function and a
reinstall of it is required."
'''
# Additional, more specific stuff for minimal Docker SDK for Python version >= 2.0.
# Note that Docker SDK for Python >= 2.0 requires Python 2.7 or newer.
DOCKER_PY_2_DOCUMENTATION = r'''
options: {}
requirements:
- "Python >= 2.7"
- "Docker SDK for Python: Please note that the L(docker-py,https://pypi.org/project/docker-py/)
Python module has been superseded by L(docker,https://pypi.org/project/docker/)
(see L(here,https://github.com/docker/docker-py/issues/1310) for details).
This module does *not* work with docker-py."
'''

View file

@ -1,272 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019, Ximon Eighteen <ximon.eighteen@gmail.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = '''
name: docker_machine
plugin_type: inventory
author: Ximon Eighteen (@ximon18)
short_description: Docker Machine inventory source
requirements:
- L(Docker Machine,https://docs.docker.com/machine/)
extends_documentation_fragment:
- constructed
description:
- Get inventory hosts from Docker Machine.
- Uses a YAML configuration file that ends with docker_machine.(yml|yaml).
- The plugin sets standard host variables C(ansible_host), C(ansible_port), C(ansible_user) and C(ansible_ssh_private_key).
- The plugin stores the Docker Machine 'env' output variables in I(dm_) prefixed host variables.
options:
plugin:
description: token that ensures this is a source file for the C(docker_machine) plugin.
required: yes
choices: ['docker_machine', 'community.general.docker_machine']
daemon_env:
description:
- Whether docker daemon connection environment variables should be fetched, and how to behave if they cannot be fetched.
- With C(require) and C(require-silently), fetch them and skip any host for which they cannot be fetched.
A warning will be issued for any skipped host if the choice is C(require).
- With C(optional) and C(optional-silently), fetch them and not skip hosts for which they cannot be fetched.
A warning will be issued for hosts where they cannot be fetched if the choice is C(optional).
- With C(skip), do not attempt to fetch the docker daemon connection environment variables.
- If fetched successfully, the variables will be prefixed with I(dm_) and stored as host variables.
type: str
choices:
- require
- require-silently
- optional
- optional-silently
- skip
default: require
running_required:
description: when true, hosts which Docker Machine indicates are in a state other than C(running) will be skipped.
type: bool
default: yes
verbose_output:
description: when true, include all available nodes metadata (e.g. Image, Region, Size) as a JSON object named C(docker_machine_node_attributes).
type: bool
default: yes
'''
EXAMPLES = '''
# Minimal example
plugin: community.general.docker_machine
# Example using constructed features to create a group per Docker Machine driver
# (https://docs.docker.com/machine/drivers/), e.g.:
# $ docker-machine create --driver digitalocean ... mymachine
# $ ansible-inventory -i ./path/to/docker-machine.yml --host=mymachine
# {
# ...
# "digitalocean": {
# "hosts": [
# "mymachine"
# ]
# ...
# }
strict: no
keyed_groups:
- separator: ''
key: docker_machine_node_attributes.DriverName
# Example grouping hosts by Digital Machine tag
strict: no
keyed_groups:
- prefix: tag
key: 'dm_tags'
# Example using compose to override the default SSH behaviour of asking the user to accept the remote host key
compose:
ansible_ssh_common_args: '"-o StrictHostKeyChecking=accept-new"'
'''
from ansible.errors import AnsibleError
from ansible.module_utils._text import to_native
from ansible.module_utils._text import to_text
from ansible.module_utils.common.process import get_bin_path
from ansible.plugins.inventory import BaseInventoryPlugin, Constructable, Cacheable
from ansible.utils.display import Display
import json
import re
import subprocess
display = Display()
class InventoryModule(BaseInventoryPlugin, Constructable, Cacheable):
''' Host inventory parser for ansible using Docker machine as source. '''
NAME = 'community.general.docker_machine'
DOCKER_MACHINE_PATH = None
def _run_command(self, args):
if not self.DOCKER_MACHINE_PATH:
try:
self.DOCKER_MACHINE_PATH = get_bin_path('docker-machine')
except ValueError as e:
raise AnsibleError(to_native(e))
command = [self.DOCKER_MACHINE_PATH]
command.extend(args)
display.debug('Executing command {0}'.format(command))
try:
result = subprocess.check_output(command)
except subprocess.CalledProcessError as e:
display.warning('Exception {0} caught while executing command {1}, this was the original exception: {2}'.format(type(e).__name__, command, e))
raise e
return to_text(result).strip()
def _get_docker_daemon_variables(self, machine_name):
'''
Capture settings from Docker Machine that would be needed to connect to the remote Docker daemon installed on
the Docker Machine remote host. Note: passing '--shell=sh' is a workaround for 'Error: Unknown shell'.
'''
try:
env_lines = self._run_command(['env', '--shell=sh', machine_name]).splitlines()
except subprocess.CalledProcessError:
# This can happen when the machine is created but provisioning is incomplete
return []
# example output of docker-machine env --shell=sh:
# export DOCKER_TLS_VERIFY="1"
# export DOCKER_HOST="tcp://134.209.204.160:2376"
# export DOCKER_CERT_PATH="/root/.docker/machine/machines/routinator"
# export DOCKER_MACHINE_NAME="routinator"
# # Run this command to configure your shell:
# # eval $(docker-machine env --shell=bash routinator)
# capture any of the DOCKER_xxx variables that were output and create Ansible host vars
# with the same name and value but with a dm_ name prefix.
vars = []
for line in env_lines:
match = re.search('(DOCKER_[^=]+)="([^"]+)"', line)
if match:
env_var_name = match.group(1)
env_var_value = match.group(2)
vars.append((env_var_name, env_var_value))
return vars
def _get_machine_names(self):
# Filter out machines that are not in the Running state as we probably can't do anything useful actions
# with them.
ls_command = ['ls', '-q']
if self.get_option('running_required'):
ls_command.extend(['--filter', 'state=Running'])
try:
ls_lines = self._run_command(ls_command)
except subprocess.CalledProcessError:
return []
return ls_lines.splitlines()
def _inspect_docker_machine_host(self, node):
try:
inspect_lines = self._run_command(['inspect', self.node])
except subprocess.CalledProcessError:
return None
return json.loads(inspect_lines)
def _ip_addr_docker_machine_host(self, node):
try:
ip_addr = self._run_command(['ip', self.node])
except subprocess.CalledProcessError:
return None
return ip_addr
def _should_skip_host(self, machine_name, env_var_tuples, daemon_env):
if not env_var_tuples:
warning_prefix = 'Unable to fetch Docker daemon env vars from Docker Machine for host {0}'.format(machine_name)
if daemon_env in ('require', 'require-silently'):
if daemon_env == 'require':
display.warning('{0}: host will be skipped'.format(warning_prefix))
return True
else: # 'optional', 'optional-silently'
if daemon_env == 'optional':
display.warning('{0}: host will lack dm_DOCKER_xxx variables'.format(warning_prefix))
return False
def _populate(self):
daemon_env = self.get_option('daemon_env')
try:
for self.node in self._get_machine_names():
self.node_attrs = self._inspect_docker_machine_host(self.node)
if not self.node_attrs:
continue
machine_name = self.node_attrs['Driver']['MachineName']
# query `docker-machine env` to obtain remote Docker daemon connection settings in the form of commands
# that could be used to set environment variables to influence a local Docker client:
if daemon_env == 'skip':
env_var_tuples = []
else:
env_var_tuples = self._get_docker_daemon_variables(machine_name)
if self._should_skip_host(machine_name, env_var_tuples, daemon_env):
continue
# add an entry in the inventory for this host
self.inventory.add_host(machine_name)
# check for valid ip address from inspect output, else explicitly use ip command to find host ip address
# this works around an issue seen with Google Compute Platform where the IP address was not available
# via the 'inspect' subcommand but was via the 'ip' subcomannd.
if self.node_attrs['Driver']['IPAddress']:
ip_addr = self.node_attrs['Driver']['IPAddress']
else:
ip_addr = self._ip_addr_docker_machine_host(self.node)
# set standard Ansible remote host connection settings to details captured from `docker-machine`
# see: https://docs.ansible.com/ansible/latest/user_guide/intro_inventory.html
self.inventory.set_variable(machine_name, 'ansible_host', ip_addr)
self.inventory.set_variable(machine_name, 'ansible_port', self.node_attrs['Driver']['SSHPort'])
self.inventory.set_variable(machine_name, 'ansible_user', self.node_attrs['Driver']['SSHUser'])
self.inventory.set_variable(machine_name, 'ansible_ssh_private_key_file', self.node_attrs['Driver']['SSHKeyPath'])
# set variables based on Docker Machine tags
tags = self.node_attrs['Driver'].get('Tags') or ''
self.inventory.set_variable(machine_name, 'dm_tags', tags)
# set variables based on Docker Machine env variables
for kv in env_var_tuples:
self.inventory.set_variable(machine_name, 'dm_{0}'.format(kv[0]), kv[1])
if self.get_option('verbose_output'):
self.inventory.set_variable(machine_name, 'docker_machine_node_attributes', self.node_attrs)
# Use constructed if applicable
strict = self.get_option('strict')
# Composed variables
self._set_composite_vars(self.get_option('compose'), self.node_attrs, machine_name, strict=strict)
# Complex groups based on jinja2 conditionals, hosts that meet the conditional are added to group
self._add_host_to_composed_groups(self.get_option('groups'), self.node_attrs, machine_name, strict=strict)
# Create groups based on variable values and add the corresponding hosts to it
self._add_host_to_keyed_groups(self.get_option('keyed_groups'), self.node_attrs, machine_name, strict=strict)
except Exception as e:
raise AnsibleError('Unable to fetch hosts from Docker Machine, this was the original exception: %s' %
to_native(e), orig_exc=e)
def verify_file(self, path):
"""Return the possibility of a file being consumable by this plugin."""
return (
super(InventoryModule, self).verify_file(path) and
path.endswith(('docker_machine.yaml', 'docker_machine.yml')))
def parse(self, inventory, loader, path, cache=True):
super(InventoryModule, self).parse(inventory, loader, path, cache)
self._read_config_data(path)
self._populate()

View file

@ -1,255 +0,0 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2018, Stefan Heitmueller <stefan.heitmueller@gmx.com>
# Copyright (c) 2018 Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = '''
name: docker_swarm
plugin_type: inventory
author:
- Stefan Heitmüller (@morph027) <stefan.heitmueller@gmx.com>
short_description: Ansible dynamic inventory plugin for Docker swarm nodes.
requirements:
- python >= 2.7
- L(Docker SDK for Python,https://docker-py.readthedocs.io/en/stable/) >= 1.10.0
extends_documentation_fragment:
- constructed
description:
- Reads inventories from the Docker swarm API.
- Uses a YAML configuration file docker_swarm.[yml|yaml].
- "The plugin returns following groups of swarm nodes: I(all) - all hosts; I(workers) - all worker nodes;
I(managers) - all manager nodes; I(leader) - the swarm leader node;
I(nonleaders) - all nodes except the swarm leader."
options:
plugin:
description: The name of this plugin, it should always be set to C(community.general.docker_swarm)
for this plugin to recognize it as it's own.
type: str
required: true
choices: [ docker_swarm, community.general.docker_swarm ]
docker_host:
description:
- Socket of a Docker swarm manager node (C(tcp), C(unix)).
- "Use C(unix://var/run/docker.sock) to connect via local socket."
type: str
required: true
aliases: [ docker_url ]
verbose_output:
description: Toggle to (not) include all available nodes metadata (e.g. C(Platform), C(Architecture), C(OS),
C(EngineVersion))
type: bool
default: yes
tls:
description: Connect using TLS without verifying the authenticity of the Docker host server.
type: bool
default: no
validate_certs:
description: Toggle if connecting using TLS with or without verifying the authenticity of the Docker
host server.
type: bool
default: no
aliases: [ tls_verify ]
client_key:
description: Path to the client's TLS key file.
type: path
aliases: [ tls_client_key, key_path ]
ca_cert:
description: Use a CA certificate when performing server verification by providing the path to a CA
certificate file.
type: path
aliases: [ tls_ca_cert, cacert_path ]
client_cert:
description: Path to the client's TLS certificate file.
type: path
aliases: [ tls_client_cert, cert_path ]
tls_hostname:
description: When verifying the authenticity of the Docker host server, provide the expected name of
the server.
type: str
ssl_version:
description: Provide a valid SSL version number. Default value determined by ssl.py module.
type: str
api_version:
description:
- The version of the Docker API running on the Docker Host.
- Defaults to the latest version of the API supported by docker-py.
type: str
aliases: [ docker_api_version ]
timeout:
description:
- The maximum amount of time in seconds to wait on a response from the API.
- If the value is not specified in the task, the value of environment variable C(DOCKER_TIMEOUT)
will be used instead. If the environment variable is not set, the default value will be used.
type: int
default: 60
aliases: [ time_out ]
include_host_uri:
description: Toggle to return the additional attribute C(ansible_host_uri) which contains the URI of the
swarm leader in format of C(tcp://172.16.0.1:2376). This value may be used without additional
modification as value of option I(docker_host) in Docker Swarm modules when connecting via API.
The port always defaults to C(2376).
type: bool
default: no
include_host_uri_port:
description: Override the detected port number included in I(ansible_host_uri)
type: int
'''
EXAMPLES = '''
# Minimal example using local docker
plugin: community.general.docker_swarm
docker_host: unix://var/run/docker.sock
# Minimal example using remote docker
plugin: community.general.docker_swarm
docker_host: tcp://my-docker-host:2375
# Example using remote docker with unverified TLS
plugin: community.general.docker_swarm
docker_host: tcp://my-docker-host:2376
tls: yes
# Example using remote docker with verified TLS and client certificate verification
plugin: community.general.docker_swarm
docker_host: tcp://my-docker-host:2376
validate_certs: yes
ca_cert: /somewhere/ca.pem
client_key: /somewhere/key.pem
client_cert: /somewhere/cert.pem
# Example using constructed features to create groups and set ansible_host
plugin: community.general.docker_swarm
docker_host: tcp://my-docker-host:2375
strict: False
keyed_groups:
# add e.g. x86_64 hosts to an arch_x86_64 group
- prefix: arch
key: 'Description.Platform.Architecture'
# add e.g. linux hosts to an os_linux group
- prefix: os
key: 'Description.Platform.OS'
# create a group per node label
# e.g. a node labeled w/ "production" ends up in group "label_production"
# hint: labels containing special characters will be converted to safe names
- key: 'Spec.Labels'
prefix: label
'''
from ansible.errors import AnsibleError
from ansible.module_utils._text import to_native
from ansible_collections.community.general.plugins.module_utils.docker.common import update_tls_hostname, get_connect_params
from ansible.plugins.inventory import BaseInventoryPlugin, Constructable
from ansible.parsing.utils.addresses import parse_address
try:
import docker
HAS_DOCKER = True
except ImportError:
HAS_DOCKER = False
class InventoryModule(BaseInventoryPlugin, Constructable):
''' Host inventory parser for ansible using Docker swarm as source. '''
NAME = 'community.general.docker_swarm'
def _fail(self, msg):
raise AnsibleError(msg)
def _populate(self):
raw_params = dict(
docker_host=self.get_option('docker_host'),
tls=self.get_option('tls'),
tls_verify=self.get_option('validate_certs'),
key_path=self.get_option('client_key'),
cacert_path=self.get_option('ca_cert'),
cert_path=self.get_option('client_cert'),
tls_hostname=self.get_option('tls_hostname'),
api_version=self.get_option('api_version'),
timeout=self.get_option('timeout'),
ssl_version=self.get_option('ssl_version'),
debug=None,
)
update_tls_hostname(raw_params)
connect_params = get_connect_params(raw_params, fail_function=self._fail)
self.client = docker.DockerClient(**connect_params)
self.inventory.add_group('all')
self.inventory.add_group('manager')
self.inventory.add_group('worker')
self.inventory.add_group('leader')
self.inventory.add_group('nonleaders')
if self.get_option('include_host_uri'):
if self.get_option('include_host_uri_port'):
host_uri_port = str(self.get_option('include_host_uri_port'))
elif self.get_option('tls') or self.get_option('validate_certs'):
host_uri_port = '2376'
else:
host_uri_port = '2375'
try:
self.nodes = self.client.nodes.list()
for self.node in self.nodes:
self.node_attrs = self.client.nodes.get(self.node.id).attrs
self.inventory.add_host(self.node_attrs['ID'])
self.inventory.add_host(self.node_attrs['ID'], group=self.node_attrs['Spec']['Role'])
self.inventory.set_variable(self.node_attrs['ID'], 'ansible_host',
self.node_attrs['Status']['Addr'])
if self.get_option('include_host_uri'):
self.inventory.set_variable(self.node_attrs['ID'], 'ansible_host_uri',
'tcp://' + self.node_attrs['Status']['Addr'] + ':' + host_uri_port)
if self.get_option('verbose_output'):
self.inventory.set_variable(self.node_attrs['ID'], 'docker_swarm_node_attributes', self.node_attrs)
if 'ManagerStatus' in self.node_attrs:
if self.node_attrs['ManagerStatus'].get('Leader'):
# This is workaround of bug in Docker when in some cases the Leader IP is 0.0.0.0
# Check moby/moby#35437 for details
swarm_leader_ip = parse_address(self.node_attrs['ManagerStatus']['Addr'])[0] or \
self.node_attrs['Status']['Addr']
if self.get_option('include_host_uri'):
self.inventory.set_variable(self.node_attrs['ID'], 'ansible_host_uri',
'tcp://' + swarm_leader_ip + ':' + host_uri_port)
self.inventory.set_variable(self.node_attrs['ID'], 'ansible_host', swarm_leader_ip)
self.inventory.add_host(self.node_attrs['ID'], group='leader')
else:
self.inventory.add_host(self.node_attrs['ID'], group='nonleaders')
else:
self.inventory.add_host(self.node_attrs['ID'], group='nonleaders')
# Use constructed if applicable
strict = self.get_option('strict')
# Composed variables
self._set_composite_vars(self.get_option('compose'),
self.node_attrs,
self.node_attrs['ID'],
strict=strict)
# Complex groups based on jinja2 conditionals, hosts that meet the conditional are added to group
self._add_host_to_composed_groups(self.get_option('groups'),
self.node_attrs,
self.node_attrs['ID'],
strict=strict)
# Create groups based on variable values and add the corresponding hosts to it
self._add_host_to_keyed_groups(self.get_option('keyed_groups'),
self.node_attrs,
self.node_attrs['ID'],
strict=strict)
except Exception as e:
raise AnsibleError('Unable to fetch hosts from Docker swarm API, this was the original exception: %s' %
to_native(e))
def verify_file(self, path):
"""Return the possibly of a file being consumable by this plugin."""
return (
super(InventoryModule, self).verify_file(path) and
path.endswith(('docker_swarm.yaml', 'docker_swarm.yml')))
def parse(self, inventory, loader, path, cache=True):
if not HAS_DOCKER:
raise AnsibleError('The Docker swarm dynamic inventory plugin requires the Docker SDK for Python: '
'https://github.com/docker/docker-py.')
super(InventoryModule, self).parse(inventory, loader, path, cache)
self._read_config_data(path)
self._populate()

File diff suppressed because it is too large Load diff

View file

@ -1,280 +0,0 @@
# (c) 2019 Piotr Wojciechowski (@wojciechowskipiotr) <piotr@it-playground.pl>
# (c) Thierry Bouvet (@tbouvet)
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import json
from time import sleep
try:
from docker.errors import APIError, NotFound
except ImportError:
# missing Docker SDK for Python handled in ansible.module_utils.docker.common
pass
from ansible.module_utils._text import to_native
from ansible_collections.community.general.plugins.module_utils.docker.common import (
AnsibleDockerClient,
LooseVersion,
)
class AnsibleDockerSwarmClient(AnsibleDockerClient):
def __init__(self, **kwargs):
super(AnsibleDockerSwarmClient, self).__init__(**kwargs)
def get_swarm_node_id(self):
"""
Get the 'NodeID' of the Swarm node or 'None' if host is not in Swarm. It returns the NodeID
of Docker host the module is executed on
:return:
NodeID of host or 'None' if not part of Swarm
"""
try:
info = self.info()
except APIError as exc:
self.fail("Failed to get node information for %s" % to_native(exc))
if info:
json_str = json.dumps(info, ensure_ascii=False)
swarm_info = json.loads(json_str)
if swarm_info['Swarm']['NodeID']:
return swarm_info['Swarm']['NodeID']
return None
def check_if_swarm_node(self, node_id=None):
"""
Checking if host is part of Docker Swarm. If 'node_id' is not provided it reads the Docker host
system information looking if specific key in output exists. If 'node_id' is provided then it tries to
read node information assuming it is run on Swarm manager. The get_node_inspect() method handles exception if
it is not executed on Swarm manager
:param node_id: Node identifier
:return:
bool: True if node is part of Swarm, False otherwise
"""
if node_id is None:
try:
info = self.info()
except APIError:
self.fail("Failed to get host information.")
if info:
json_str = json.dumps(info, ensure_ascii=False)
swarm_info = json.loads(json_str)
if swarm_info['Swarm']['NodeID']:
return True
if swarm_info['Swarm']['LocalNodeState'] in ('active', 'pending', 'locked'):
return True
return False
else:
try:
node_info = self.get_node_inspect(node_id=node_id)
except APIError:
return
if node_info['ID'] is not None:
return True
return False
def check_if_swarm_manager(self):
"""
Checks if node role is set as Manager in Swarm. The node is the docker host on which module action
is performed. The inspect_swarm() will fail if node is not a manager
:return: True if node is Swarm Manager, False otherwise
"""
try:
self.inspect_swarm()
return True
except APIError:
return False
def fail_task_if_not_swarm_manager(self):
"""
If host is not a swarm manager then Ansible task on this host should end with 'failed' state
"""
if not self.check_if_swarm_manager():
self.fail("Error running docker swarm module: must run on swarm manager node")
def check_if_swarm_worker(self):
"""
Checks if node role is set as Worker in Swarm. The node is the docker host on which module action
is performed. Will fail if run on host that is not part of Swarm via check_if_swarm_node()
:return: True if node is Swarm Worker, False otherwise
"""
if self.check_if_swarm_node() and not self.check_if_swarm_manager():
return True
return False
def check_if_swarm_node_is_down(self, node_id=None, repeat_check=1):
"""
Checks if node status on Swarm manager is 'down'. If node_id is provided it query manager about
node specified in parameter, otherwise it query manager itself. If run on Swarm Worker node or
host that is not part of Swarm it will fail the playbook
:param repeat_check: number of check attempts with 5 seconds delay between them, by default check only once
:param node_id: node ID or name, if None then method will try to get node_id of host module run on
:return:
True if node is part of swarm but its state is down, False otherwise
"""
if repeat_check < 1:
repeat_check = 1
if node_id is None:
node_id = self.get_swarm_node_id()
for retry in range(0, repeat_check):
if retry > 0:
sleep(5)
node_info = self.get_node_inspect(node_id=node_id)
if node_info['Status']['State'] == 'down':
return True
return False
def get_node_inspect(self, node_id=None, skip_missing=False):
"""
Returns Swarm node info as in 'docker node inspect' command about single node
:param skip_missing: if True then function will return None instead of failing the task
:param node_id: node ID or name, if None then method will try to get node_id of host module run on
:return:
Single node information structure
"""
if node_id is None:
node_id = self.get_swarm_node_id()
if node_id is None:
self.fail("Failed to get node information.")
try:
node_info = self.inspect_node(node_id=node_id)
except APIError as exc:
if exc.status_code == 503:
self.fail("Cannot inspect node: To inspect node execute module on Swarm Manager")
if exc.status_code == 404:
if skip_missing:
return None
self.fail("Error while reading from Swarm manager: %s" % to_native(exc))
except Exception as exc:
self.fail("Error inspecting swarm node: %s" % exc)
json_str = json.dumps(node_info, ensure_ascii=False)
node_info = json.loads(json_str)
if 'ManagerStatus' in node_info:
if node_info['ManagerStatus'].get('Leader'):
# This is workaround of bug in Docker when in some cases the Leader IP is 0.0.0.0
# Check moby/moby#35437 for details
count_colons = node_info['ManagerStatus']['Addr'].count(":")
if count_colons == 1:
swarm_leader_ip = node_info['ManagerStatus']['Addr'].split(":", 1)[0] or node_info['Status']['Addr']
else:
swarm_leader_ip = node_info['Status']['Addr']
node_info['Status']['Addr'] = swarm_leader_ip
return node_info
def get_all_nodes_inspect(self):
"""
Returns Swarm node info as in 'docker node inspect' command about all registered nodes
:return:
Structure with information about all nodes
"""
try:
node_info = self.nodes()
except APIError as exc:
if exc.status_code == 503:
self.fail("Cannot inspect node: To inspect node execute module on Swarm Manager")
self.fail("Error while reading from Swarm manager: %s" % to_native(exc))
except Exception as exc:
self.fail("Error inspecting swarm node: %s" % exc)
json_str = json.dumps(node_info, ensure_ascii=False)
node_info = json.loads(json_str)
return node_info
def get_all_nodes_list(self, output='short'):
"""
Returns list of nodes registered in Swarm
:param output: Defines format of returned data
:return:
If 'output' is 'short' then return data is list of nodes hostnames registered in Swarm,
if 'output' is 'long' then returns data is list of dict containing the attributes as in
output of command 'docker node ls'
"""
nodes_list = []
nodes_inspect = self.get_all_nodes_inspect()
if nodes_inspect is None:
return None
if output == 'short':
for node in nodes_inspect:
nodes_list.append(node['Description']['Hostname'])
elif output == 'long':
for node in nodes_inspect:
node_property = {}
node_property.update({'ID': node['ID']})
node_property.update({'Hostname': node['Description']['Hostname']})
node_property.update({'Status': node['Status']['State']})
node_property.update({'Availability': node['Spec']['Availability']})
if 'ManagerStatus' in node:
if node['ManagerStatus']['Leader'] is True:
node_property.update({'Leader': True})
node_property.update({'ManagerStatus': node['ManagerStatus']['Reachability']})
node_property.update({'EngineVersion': node['Description']['Engine']['EngineVersion']})
nodes_list.append(node_property)
else:
return None
return nodes_list
def get_node_name_by_id(self, nodeid):
return self.get_node_inspect(nodeid)['Description']['Hostname']
def get_unlock_key(self):
if self.docker_py_version < LooseVersion('2.7.0'):
return None
return super(AnsibleDockerSwarmClient, self).get_unlock_key()
def get_service_inspect(self, service_id, skip_missing=False):
"""
Returns Swarm service info as in 'docker service inspect' command about single service
:param service_id: service ID or name
:param skip_missing: if True then function will return None instead of failing the task
:return:
Single service information structure
"""
try:
service_info = self.inspect_service(service_id)
except NotFound as exc:
if skip_missing is False:
self.fail("Error while reading from Swarm manager: %s" % to_native(exc))
else:
return None
except APIError as exc:
if exc.status_code == 503:
self.fail("Cannot inspect service: To inspect service execute module on Swarm Manager")
self.fail("Error inspecting swarm service: %s" % exc)
except Exception as exc:
self.fail("Error inspecting swarm service: %s" % exc)
json_str = json.dumps(service_info, ensure_ascii=False)
service_info = json.loads(json_str)
return service_info

File diff suppressed because it is too large Load diff

View file

@ -1,299 +0,0 @@
#!/usr/bin/python
#
# Copyright 2016 Red Hat | Ansible
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
---
module: docker_config
short_description: Manage docker configs.
description:
- Create and remove Docker configs in a Swarm environment. Similar to C(docker config create) and C(docker config rm).
- Adds to the metadata of new configs 'ansible_key', an encrypted hash representation of the data, which is then used
in future runs to test if a config has changed. If 'ansible_key' is not present, then a config will not be updated
unless the I(force) option is set.
- Updates to configs are performed by removing the config and creating it again.
options:
data:
description:
- The value of the config. Required when state is C(present).
type: str
data_is_b64:
description:
- If set to C(true), the data is assumed to be Base64 encoded and will be
decoded before being used.
- To use binary I(data), it is better to keep it Base64 encoded and let it
be decoded by this option.
type: bool
default: no
labels:
description:
- "A map of key:value meta data, where both the I(key) and I(value) are expected to be a string."
- If new meta data is provided, or existing meta data is modified, the config will be updated by removing it and creating it again.
type: dict
force:
description:
- Use with state C(present) to always remove and recreate an existing config.
- If C(true), an existing config will be replaced, even if it has not been changed.
type: bool
default: no
name:
description:
- The name of the config.
type: str
required: yes
state:
description:
- Set to C(present), if the config should exist, and C(absent), if it should not.
type: str
default: present
choices:
- absent
- present
extends_documentation_fragment:
- community.general.docker
- community.general.docker.docker_py_2_documentation
requirements:
- "L(Docker SDK for Python,https://docker-py.readthedocs.io/en/stable/) >= 2.6.0"
- "Docker API >= 1.30"
author:
- Chris Houseknecht (@chouseknecht)
- John Hu (@ushuz)
'''
EXAMPLES = '''
- name: Create config foo (from a file on the control machine)
community.general.docker_config:
name: foo
# If the file is JSON or binary, Ansible might modify it (because
# it is first decoded and later re-encoded). Base64-encoding the
# file directly after reading it prevents this to happen.
data: "{{ lookup('file', '/path/to/config/file') | b64encode }}"
data_is_b64: true
state: present
- name: Change the config data
community.general.docker_config:
name: foo
data: Goodnight everyone!
labels:
bar: baz
one: '1'
state: present
- name: Add a new label
community.general.docker_config:
name: foo
data: Goodnight everyone!
labels:
bar: baz
one: '1'
# Adding a new label will cause a remove/create of the config
two: '2'
state: present
- name: No change
community.general.docker_config:
name: foo
data: Goodnight everyone!
labels:
bar: baz
one: '1'
# Even though 'two' is missing, there is no change to the existing config
state: present
- name: Update an existing label
community.general.docker_config:
name: foo
data: Goodnight everyone!
labels:
bar: monkey # Changing a label will cause a remove/create of the config
one: '1'
state: present
- name: Force the (re-)creation of the config
community.general.docker_config:
name: foo
data: Goodnight everyone!
force: yes
state: present
- name: Remove config foo
community.general.docker_config:
name: foo
state: absent
'''
RETURN = '''
config_id:
description:
- The ID assigned by Docker to the config object.
returned: success and I(state) is C(present)
type: str
sample: 'hzehrmyjigmcp2gb6nlhmjqcv'
'''
import base64
import hashlib
import traceback
try:
from docker.errors import DockerException, APIError
except ImportError:
# missing Docker SDK for Python handled in ansible.module_utils.docker.common
pass
from ansible_collections.community.general.plugins.module_utils.docker.common import (
AnsibleDockerClient,
DockerBaseClass,
compare_generic,
RequestException,
)
from ansible.module_utils._text import to_native, to_bytes
class ConfigManager(DockerBaseClass):
def __init__(self, client, results):
super(ConfigManager, self).__init__()
self.client = client
self.results = results
self.check_mode = self.client.check_mode
parameters = self.client.module.params
self.name = parameters.get('name')
self.state = parameters.get('state')
self.data = parameters.get('data')
if self.data is not None:
if parameters.get('data_is_b64'):
self.data = base64.b64decode(self.data)
else:
self.data = to_bytes(self.data)
self.labels = parameters.get('labels')
self.force = parameters.get('force')
self.data_key = None
def __call__(self):
if self.state == 'present':
self.data_key = hashlib.sha224(self.data).hexdigest()
self.present()
elif self.state == 'absent':
self.absent()
def get_config(self):
''' Find an existing config. '''
try:
configs = self.client.configs(filters={'name': self.name})
except APIError as exc:
self.client.fail("Error accessing config %s: %s" % (self.name, to_native(exc)))
for config in configs:
if config['Spec']['Name'] == self.name:
return config
return None
def create_config(self):
''' Create a new config '''
config_id = None
# We can't see the data after creation, so adding a label we can use for idempotency check
labels = {
'ansible_key': self.data_key
}
if self.labels:
labels.update(self.labels)
try:
if not self.check_mode:
config_id = self.client.create_config(self.name, self.data, labels=labels)
except APIError as exc:
self.client.fail("Error creating config: %s" % to_native(exc))
if isinstance(config_id, dict):
config_id = config_id['ID']
return config_id
def present(self):
''' Handles state == 'present', creating or updating the config '''
config = self.get_config()
if config:
self.results['config_id'] = config['ID']
data_changed = False
attrs = config.get('Spec', {})
if attrs.get('Labels', {}).get('ansible_key'):
if attrs['Labels']['ansible_key'] != self.data_key:
data_changed = True
labels_changed = not compare_generic(self.labels, attrs.get('Labels'), 'allow_more_present', 'dict')
if data_changed or labels_changed or self.force:
# if something changed or force, delete and re-create the config
self.absent()
config_id = self.create_config()
self.results['changed'] = True
self.results['config_id'] = config_id
else:
self.results['changed'] = True
self.results['config_id'] = self.create_config()
def absent(self):
''' Handles state == 'absent', removing the config '''
config = self.get_config()
if config:
try:
if not self.check_mode:
self.client.remove_config(config['ID'])
except APIError as exc:
self.client.fail("Error removing config %s: %s" % (self.name, to_native(exc)))
self.results['changed'] = True
def main():
argument_spec = dict(
name=dict(type='str', required=True),
state=dict(type='str', default='present', choices=['absent', 'present']),
data=dict(type='str'),
data_is_b64=dict(type='bool', default=False),
labels=dict(type='dict'),
force=dict(type='bool', default=False)
)
required_if = [
('state', 'present', ['data'])
]
client = AnsibleDockerClient(
argument_spec=argument_spec,
supports_check_mode=True,
required_if=required_if,
min_docker_version='2.6.0',
min_docker_api_version='1.30',
)
try:
results = dict(
changed=False,
)
ConfigManager(client, results)()
client.module.exit_json(**results)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
except RequestException as e:
client.fail('An unexpected requests error occurred when docker-py tried to talk to the docker daemon: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':
main()

File diff suppressed because it is too large Load diff

View file

@ -1,145 +0,0 @@
#!/usr/bin/python
#
# Copyright 2016 Red Hat | Ansible
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
---
module: docker_container_info
short_description: Retrieves facts about docker container
description:
- Retrieves facts about a docker container.
- Essentially returns the output of C(docker inspect <name>), similar to what M(community.general.docker_container)
returns for a non-absent container.
options:
name:
description:
- The name of the container to inspect.
- When identifying an existing container name may be a name or a long or short container ID.
type: str
required: yes
extends_documentation_fragment:
- community.general.docker
- community.general.docker.docker_py_1_documentation
author:
- "Felix Fontein (@felixfontein)"
requirements:
- "L(Docker SDK for Python,https://docker-py.readthedocs.io/en/stable/) >= 1.8.0 (use L(docker-py,https://pypi.org/project/docker-py/) for Python 2.6)"
- "Docker API >= 1.20"
'''
EXAMPLES = '''
- name: Get infos on container
community.general.docker_container_info:
name: mydata
register: result
- name: Does container exist?
ansible.builtin.debug:
msg: "The container {{ 'exists' if result.exists else 'does not exist' }}"
- name: Print information about container
ansible.builtin.debug:
var: result.container
when: result.exists
'''
RETURN = '''
exists:
description:
- Returns whether the container exists.
type: bool
returned: always
sample: true
container:
description:
- Facts representing the current state of the container. Matches the docker inspection output.
- Will be C(none) if container does not exist.
returned: always
type: dict
sample: '{
"AppArmorProfile": "",
"Args": [],
"Config": {
"AttachStderr": false,
"AttachStdin": false,
"AttachStdout": false,
"Cmd": [
"/usr/bin/supervisord"
],
"Domainname": "",
"Entrypoint": null,
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
"ExposedPorts": {
"443/tcp": {},
"80/tcp": {}
},
"Hostname": "8e47bf643eb9",
"Image": "lnmp_nginx:v1",
"Labels": {},
"OnBuild": null,
"OpenStdin": false,
"StdinOnce": false,
"Tty": false,
"User": "",
"Volumes": {
"/tmp/lnmp/nginx-sites/logs/": {}
},
...
}'
'''
import traceback
try:
from docker.errors import DockerException
except ImportError:
# missing Docker SDK for Python handled in ansible.module_utils.docker.common
pass
from ansible_collections.community.general.plugins.module_utils.docker.common import (
AnsibleDockerClient,
RequestException,
)
def main():
argument_spec = dict(
name=dict(type='str', required=True),
)
client = AnsibleDockerClient(
argument_spec=argument_spec,
supports_check_mode=True,
min_docker_api_version='1.20',
)
try:
container = client.get_container(client.module.params['name'])
client.module.exit_json(
changed=False,
exists=(True if container else False),
container=container,
)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
except RequestException as e:
client.fail('An unexpected requests error occurred when docker-py tried to talk to the docker daemon: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':
main()

View file

@ -1,343 +0,0 @@
#!/usr/bin/python
#
# (c) 2019 Piotr Wojciechowski <piotr@it-playground.pl>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
---
module: docker_host_info
short_description: Retrieves facts about docker host and lists of objects of the services.
description:
- Retrieves facts about a docker host.
- Essentially returns the output of C(docker system info).
- The module also allows to list object names for containers, images, networks and volumes.
It also allows to query information on disk usage.
- The output differs depending on API version of the docker daemon.
- If the docker daemon cannot be contacted or does not meet the API version requirements,
the module will fail.
options:
containers:
description:
- Whether to list containers.
type: bool
default: no
containers_filters:
description:
- A dictionary of filter values used for selecting containers to list.
- "For example, C(until: 24h)."
- See L(the docker documentation,https://docs.docker.com/engine/reference/commandline/container_prune/#filtering)
for more information on possible filters.
type: dict
images:
description:
- Whether to list images.
type: bool
default: no
images_filters:
description:
- A dictionary of filter values used for selecting images to list.
- "For example, C(dangling: true)."
- See L(the docker documentation,https://docs.docker.com/engine/reference/commandline/image_prune/#filtering)
for more information on possible filters.
type: dict
networks:
description:
- Whether to list networks.
type: bool
default: no
networks_filters:
description:
- A dictionary of filter values used for selecting networks to list.
- See L(the docker documentation,https://docs.docker.com/engine/reference/commandline/network_prune/#filtering)
for more information on possible filters.
type: dict
volumes:
description:
- Whether to list volumes.
type: bool
default: no
volumes_filters:
description:
- A dictionary of filter values used for selecting volumes to list.
- See L(the docker documentation,https://docs.docker.com/engine/reference/commandline/volume_prune/#filtering)
for more information on possible filters.
type: dict
disk_usage:
description:
- Summary information on used disk space by all Docker layers.
- The output is a sum of images, volumes, containers and build cache.
type: bool
default: no
verbose_output:
description:
- When set to C(yes) and I(networks), I(volumes), I(images), I(containers) or I(disk_usage) is set to C(yes)
then output will contain verbose information about objects matching the full output of API method.
For details see the documentation of your version of Docker API at L(https://docs.docker.com/engine/api/).
- The verbose output in this module contains only subset of information returned by I(_info) module
for each type of the objects.
type: bool
default: no
extends_documentation_fragment:
- community.general.docker
- community.general.docker.docker_py_1_documentation
author:
- Piotr Wojciechowski (@WojciechowskiPiotr)
requirements:
- "L(Docker SDK for Python,https://docker-py.readthedocs.io/en/stable/) >= 1.10.0 (use L(docker-py,https://pypi.org/project/docker-py/) for Python 2.6)"
- "Docker API >= 1.21"
'''
EXAMPLES = '''
- name: Get info on docker host
community.general.docker_host_info:
register: result
- name: Get info on docker host and list images
community.general.docker_host_info:
images: yes
register: result
- name: Get info on docker host and list images matching the filter
community.general.docker_host_info:
images: yes
images_filters:
label: "mylabel"
register: result
- name: Get info on docker host and verbose list images
community.general.docker_host_info:
images: yes
verbose_output: yes
register: result
- name: Get info on docker host and used disk space
community.general.docker_host_info:
disk_usage: yes
register: result
- ansible.builtin.debug:
var: result.host_info
'''
RETURN = '''
can_talk_to_docker:
description:
- Will be C(true) if the module can talk to the docker daemon.
returned: both on success and on error
type: bool
host_info:
description:
- Facts representing the basic state of the docker host. Matches the C(docker system info) output.
returned: always
type: dict
volumes:
description:
- List of dict objects containing the basic information about each volume.
Keys matches the C(docker volume ls) output unless I(verbose_output=yes).
See description for I(verbose_output).
returned: When I(volumes) is C(yes)
type: list
elements: dict
networks:
description:
- List of dict objects containing the basic information about each network.
Keys matches the C(docker network ls) output unless I(verbose_output=yes).
See description for I(verbose_output).
returned: When I(networks) is C(yes)
type: list
elements: dict
containers:
description:
- List of dict objects containing the basic information about each container.
Keys matches the C(docker container ls) output unless I(verbose_output=yes).
See description for I(verbose_output).
returned: When I(containers) is C(yes)
type: list
elements: dict
images:
description:
- List of dict objects containing the basic information about each image.
Keys matches the C(docker image ls) output unless I(verbose_output=yes).
See description for I(verbose_output).
returned: When I(images) is C(yes)
type: list
elements: dict
disk_usage:
description:
- Information on summary disk usage by images, containers and volumes on docker host
unless I(verbose_output=yes). See description for I(verbose_output).
returned: When I(disk_usage) is C(yes)
type: dict
'''
import traceback
from ansible_collections.community.general.plugins.module_utils.docker.common import (
AnsibleDockerClient,
DockerBaseClass,
RequestException,
)
from ansible.module_utils._text import to_native
try:
from docker.errors import DockerException, APIError
except ImportError:
# Missing Docker SDK for Python handled in ansible.module_utils.docker.common
pass
from ansible_collections.community.general.plugins.module_utils.docker.common import clean_dict_booleans_for_docker_api
class DockerHostManager(DockerBaseClass):
def __init__(self, client, results):
super(DockerHostManager, self).__init__()
self.client = client
self.results = results
self.verbose_output = self.client.module.params['verbose_output']
listed_objects = ['volumes', 'networks', 'containers', 'images']
self.results['host_info'] = self.get_docker_host_info()
if self.client.module.params['disk_usage']:
self.results['disk_usage'] = self.get_docker_disk_usage_facts()
for docker_object in listed_objects:
if self.client.module.params[docker_object]:
returned_name = docker_object
filter_name = docker_object + "_filters"
filters = clean_dict_booleans_for_docker_api(client.module.params.get(filter_name))
self.results[returned_name] = self.get_docker_items_list(docker_object, filters)
def get_docker_host_info(self):
try:
return self.client.info()
except APIError as exc:
self.client.fail("Error inspecting docker host: %s" % to_native(exc))
def get_docker_disk_usage_facts(self):
try:
if self.verbose_output:
return self.client.df()
else:
return dict(LayersSize=self.client.df()['LayersSize'])
except APIError as exc:
self.client.fail("Error inspecting docker host: %s" % to_native(exc))
def get_docker_items_list(self, docker_object=None, filters=None, verbose=False):
items = None
items_list = []
header_containers = ['Id', 'Image', 'Command', 'Created', 'Status', 'Ports', 'Names']
header_volumes = ['Driver', 'Name']
header_images = ['Id', 'RepoTags', 'Created', 'Size']
header_networks = ['Id', 'Driver', 'Name', 'Scope']
filter_arg = dict()
if filters:
filter_arg['filters'] = filters
try:
if docker_object == 'containers':
items = self.client.containers(**filter_arg)
elif docker_object == 'networks':
items = self.client.networks(**filter_arg)
elif docker_object == 'images':
items = self.client.images(**filter_arg)
elif docker_object == 'volumes':
items = self.client.volumes(**filter_arg)
except APIError as exc:
self.client.fail("Error inspecting docker host for object '%s': %s" %
(docker_object, to_native(exc)))
if self.verbose_output:
if docker_object != 'volumes':
return items
else:
return items['Volumes']
if docker_object == 'volumes':
items = items['Volumes']
for item in items:
item_record = dict()
if docker_object == 'containers':
for key in header_containers:
item_record[key] = item.get(key)
elif docker_object == 'networks':
for key in header_networks:
item_record[key] = item.get(key)
elif docker_object == 'images':
for key in header_images:
item_record[key] = item.get(key)
elif docker_object == 'volumes':
for key in header_volumes:
item_record[key] = item.get(key)
items_list.append(item_record)
return items_list
def main():
argument_spec = dict(
containers=dict(type='bool', default=False),
containers_filters=dict(type='dict'),
images=dict(type='bool', default=False),
images_filters=dict(type='dict'),
networks=dict(type='bool', default=False),
networks_filters=dict(type='dict'),
volumes=dict(type='bool', default=False),
volumes_filters=dict(type='dict'),
disk_usage=dict(type='bool', default=False),
verbose_output=dict(type='bool', default=False),
)
option_minimal_versions = dict(
network_filters=dict(docker_py_version='2.0.2'),
disk_usage=dict(docker_py_version='2.2.0'),
)
client = AnsibleDockerClient(
argument_spec=argument_spec,
supports_check_mode=True,
min_docker_version='1.10.0',
min_docker_api_version='1.21',
option_minimal_versions=option_minimal_versions,
fail_results=dict(
can_talk_to_docker=False,
),
)
client.fail_results['can_talk_to_docker'] = True
try:
results = dict(
changed=False,
)
DockerHostManager(client, results)
client.module.exit_json(**results)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
except RequestException as e:
client.fail('An unexpected requests error occurred when docker-py tried to talk to the docker daemon: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':
main()

View file

@ -1,965 +0,0 @@
#!/usr/bin/python
#
# Copyright 2016 Red Hat | Ansible
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
---
module: docker_image
short_description: Manage docker images.
description:
- Build, load or pull an image, making the image available for creating containers. Also supports tagging an
image into a repository and archiving an image to a .tar file.
- Since Ansible 2.8, it is recommended to explicitly specify the image's source (I(source) can be C(build),
C(load), C(pull) or C(local)). This will be required from community.general 2.0.0 on.
options:
source:
description:
- "Determines where the module will try to retrieve the image from."
- "Use C(build) to build the image from a C(Dockerfile). I(build.path) must
be specified when this value is used."
- "Use C(load) to load the image from a C(.tar) file. I(load_path) must
be specified when this value is used."
- "Use C(pull) to pull the image from a registry."
- "Use C(local) to make sure that the image is already available on the local
docker daemon, i.e. do not try to build, pull or load the image."
- "Before community.general 2.0.0, the value of this option will be auto-detected
to be backwards compatible, but a warning will be issued if it is not
explicitly specified. From community.general 2.0.0 on, auto-detection will be disabled
and this option will be made mandatory."
type: str
choices:
- build
- load
- pull
- local
build:
description:
- "Specifies options used for building images."
type: dict
suboptions:
cache_from:
description:
- List of image names to consider as cache source.
type: list
elements: str
dockerfile:
description:
- Use with state C(present) and source C(build) to provide an alternate name for the Dockerfile to use when building an image.
- This can also include a relative path (relative to I(path)).
type: str
http_timeout:
description:
- Timeout for HTTP requests during the image build operation. Provide a positive integer value for the number of
seconds.
type: int
path:
description:
- Use with state 'present' to build an image. Will be the path to a directory containing the context and
Dockerfile for building an image.
type: path
required: yes
pull:
description:
- When building an image downloads any updates to the FROM image in Dockerfile.
- The default is currently C(yes). This will change to C(no) in community.general 2.0.0.
type: bool
rm:
description:
- Remove intermediate containers after build.
type: bool
default: yes
network:
description:
- The network to use for C(RUN) build instructions.
type: str
nocache:
description:
- Do not use cache when building an image.
type: bool
default: no
etc_hosts:
description:
- Extra hosts to add to C(/etc/hosts) in building containers, as a mapping of hostname to IP address.
type: dict
args:
description:
- Provide a dictionary of C(key:value) build arguments that map to Dockerfile ARG directive.
- Docker expects the value to be a string. For convenience any non-string values will be converted to strings.
- Requires Docker API >= 1.21.
type: dict
container_limits:
description:
- A dictionary of limits applied to each container created by the build process.
type: dict
suboptions:
memory:
description:
- Set memory limit for build.
type: int
memswap:
description:
- Total memory (memory + swap), -1 to disable swap.
type: int
cpushares:
description:
- CPU shares (relative weight).
type: int
cpusetcpus:
description:
- CPUs in which to allow execution, e.g., "0-3", "0,1".
type: str
use_config_proxy:
description:
- If set to C(yes) and a proxy configuration is specified in the docker client configuration
(by default C($HOME/.docker/config.json)), the corresponding environment variables will
be set in the container being built.
- Needs Docker SDK for Python >= 3.7.0.
type: bool
target:
description:
- When building an image specifies an intermediate build stage by
name as a final stage for the resulting image.
type: str
archive_path:
description:
- Use with state C(present) to archive an image to a .tar file.
type: path
load_path:
description:
- Use with state C(present) to load an image from a .tar file.
- Set I(source) to C(load) if you want to load the image. The option will
be set automatically before community.general 2.0.0 if this option is used (except
if I(path) is specified as well, in which case building will take precedence).
From community.general 2.0.0 on, you have to set I(source) to C(load).
type: path
dockerfile:
description:
- Use with state C(present) and source C(build) to provide an alternate name for the Dockerfile to use when building an image.
- This can also include a relative path (relative to I(path)).
- Please use I(build.dockerfile) instead. This option will be removed in community.general 2.0.0.
type: str
force:
description:
- Use with state I(absent) to un-tag and remove all images matching the specified name. Use with state
C(present) to build, load or pull an image when the image already exists. Also use with state C(present)
to force tagging an image.
- Please stop using this option, and use the more specialized force options
I(force_source), I(force_absent) and I(force_tag) instead.
- This option will be removed in community.general 2.0.0.
type: bool
force_source:
description:
- Use with state C(present) to build, load or pull an image (depending on the
value of the I(source) option) when the image already exists.
type: bool
default: false
force_absent:
description:
- Use with state I(absent) to un-tag and remove all images matching the specified name.
type: bool
default: false
force_tag:
description:
- Use with state C(present) to force tagging an image.
type: bool
default: false
http_timeout:
description:
- Timeout for HTTP requests during the image build operation. Provide a positive integer value for the number of
seconds.
- Please use I(build.http_timeout) instead. This option will be removed in community.general 2.0.0.
type: int
name:
description:
- "Image name. Name format will be one of: name, repository/name, registry_server:port/name.
When pushing or pulling an image the name can optionally include the tag by appending ':tag_name'."
- Note that image IDs (hashes) are not supported.
type: str
required: yes
path:
description:
- Use with state 'present' to build an image. Will be the path to a directory containing the context and
Dockerfile for building an image.
- Set I(source) to C(build) if you want to build the image. The option will
be set automatically before community.general 2.0.0 if this option is used. From community.general 2.0.0
on, you have to set I(source) to C(build).
- Please use I(build.path) instead. This option will be removed in community.general 2.0.0.
type: path
aliases:
- build_path
pull:
description:
- When building an image downloads any updates to the FROM image in Dockerfile.
- Please use I(build.pull) instead. This option will be removed in community.general 2.0.0.
- The default is currently C(yes). This will change to C(no) in community.general 2.0.0.
type: bool
push:
description:
- Push the image to the registry. Specify the registry as part of the I(name) or I(repository) parameter.
type: bool
default: no
rm:
description:
- Remove intermediate containers after build.
- Please use I(build.rm) instead. This option will be removed in community.general 2.0.0.
type: bool
default: yes
nocache:
description:
- Do not use cache when building an image.
- Please use I(build.nocache) instead. This option will be removed in community.general 2.0.0.
type: bool
default: no
repository:
description:
- Full path to a repository. Use with state C(present) to tag the image into the repository. Expects
format I(repository:tag). If no tag is provided, will use the value of the C(tag) parameter or I(latest).
type: str
state:
description:
- Make assertions about the state of an image.
- When C(absent) an image will be removed. Use the force option to un-tag and remove all images
matching the provided name.
- When C(present) check if an image exists using the provided name and tag. If the image is not found or the
force option is used, the image will either be pulled, built or loaded, depending on the I(source) option.
- By default the image will be pulled from Docker Hub, or the registry specified in the image's name. Note that
this will change in community.general 2.0.0, so to make sure that you are pulling, set I(source) to C(pull). To build
the image, provide a I(path) value set to a directory containing a context and Dockerfile, and set I(source)
to C(build). To load an image, specify I(load_path) to provide a path to an archive file. To tag an image to
a repository, provide a I(repository) path. If the name contains a repository path, it will be pushed.
- "*Note:* C(state=build) is DEPRECATED and will be removed in community.general 2.0.0. Specifying C(build) will behave the
same as C(present)."
type: str
default: present
choices:
- absent
- present
- build
tag:
description:
- Used to select an image when pulling. Will be added to the image when pushing, tagging or building. Defaults to
I(latest).
- If I(name) parameter format is I(name:tag), then tag value from I(name) will take precedence.
type: str
default: latest
buildargs:
description:
- Provide a dictionary of C(key:value) build arguments that map to Dockerfile ARG directive.
- Docker expects the value to be a string. For convenience any non-string values will be converted to strings.
- Requires Docker API >= 1.21.
- Please use I(build.args) instead. This option will be removed in community.general 2.0.0.
type: dict
container_limits:
description:
- A dictionary of limits applied to each container created by the build process.
- Please use I(build.container_limits) instead. This option will be removed in community.general 2.0.0.
type: dict
suboptions:
memory:
description:
- Set memory limit for build.
type: int
memswap:
description:
- Total memory (memory + swap), -1 to disable swap.
type: int
cpushares:
description:
- CPU shares (relative weight).
type: int
cpusetcpus:
description:
- CPUs in which to allow execution, e.g., "0-3", "0,1".
type: str
use_tls:
description:
- "DEPRECATED. Whether to use tls to connect to the docker daemon. Set to
C(encrypt) to use TLS. And set to C(verify) to use TLS and verify that
the server's certificate is valid for the server."
- "*Note:* If you specify this option, it will set the value of the I(tls) or
I(validate_certs) parameters if not set to C(no)."
- Will be removed in community.general 2.0.0.
type: str
choices:
- 'no'
- 'encrypt'
- 'verify'
extends_documentation_fragment:
- community.general.docker
- community.general.docker.docker_py_1_documentation
requirements:
- "L(Docker SDK for Python,https://docker-py.readthedocs.io/en/stable/) >= 1.8.0 (use L(docker-py,https://pypi.org/project/docker-py/) for Python 2.6)"
- "Docker API >= 1.20"
author:
- Pavel Antonov (@softzilla)
- Chris Houseknecht (@chouseknecht)
- Sorin Sbarnea (@ssbarnea)
'''
EXAMPLES = '''
- name: Pull an image
community.general.docker_image:
name: pacur/centos-7
source: pull
- name: Tag and push to docker hub
community.general.docker_image:
name: pacur/centos-7:56
repository: dcoppenhagan/myimage:7.56
push: yes
source: local
- name: Tag and push to local registry
community.general.docker_image:
# Image will be centos:7
name: centos
# Will be pushed to localhost:5000/centos:7
repository: localhost:5000/centos
tag: 7
push: yes
source: local
- name: Add tag latest to image
community.general.docker_image:
name: myimage:7.1.2
repository: myimage:latest
# As 'latest' usually already is present, we need to enable overwriting of existing tags:
force_tag: yes
source: local
- name: Remove image
community.general.docker_image:
state: absent
name: registry.ansible.com/chouseknecht/sinatra
tag: v1
- name: Build an image and push it to a private repo
community.general.docker_image:
build:
path: ./sinatra
name: registry.ansible.com/chouseknecht/sinatra
tag: v1
push: yes
source: build
- name: Archive image
community.general.docker_image:
name: registry.ansible.com/chouseknecht/sinatra
tag: v1
archive_path: my_sinatra.tar
source: local
- name: Load image from archive and push to a private registry
community.general.docker_image:
name: localhost:5000/myimages/sinatra
tag: v1
push: yes
load_path: my_sinatra.tar
source: load
- name: Build image and with build args
community.general.docker_image:
name: myimage
build:
path: /path/to/build/dir
args:
log_volume: /var/log/myapp
listen_port: 8080
source: build
- name: Build image using cache source
community.general.docker_image:
name: myimage:latest
build:
path: /path/to/build/dir
# Use as cache source for building myimage
cache_from:
- nginx:latest
- alpine:3.8
source: build
'''
RETURN = '''
image:
description: Image inspection results for the affected image.
returned: success
type: dict
sample: {}
stdout:
description: Docker build output when building an image.
returned: success
type: str
sample: ""
version_added: 1.3.0
'''
import errno
import os
import re
import traceback
from distutils.version import LooseVersion
from ansible_collections.community.general.plugins.module_utils.docker.common import (
clean_dict_booleans_for_docker_api,
docker_version,
AnsibleDockerClient,
DockerBaseClass,
is_image_name_id,
is_valid_tag,
RequestException,
)
from ansible.module_utils._text import to_native
if docker_version is not None:
try:
if LooseVersion(docker_version) >= LooseVersion('2.0.0'):
from docker.auth import resolve_repository_name
else:
from docker.auth.auth import resolve_repository_name
from docker.utils.utils import parse_repository_tag
from docker.errors import DockerException
except ImportError:
# missing Docker SDK for Python handled in module_utils.docker.common
pass
class ImageManager(DockerBaseClass):
def __init__(self, client, results):
super(ImageManager, self).__init__()
self.client = client
self.results = results
parameters = self.client.module.params
self.check_mode = self.client.check_mode
self.source = parameters['source']
build = parameters['build'] or dict()
self.archive_path = parameters.get('archive_path')
self.cache_from = build.get('cache_from')
self.container_limits = build.get('container_limits')
self.dockerfile = build.get('dockerfile')
self.force_source = parameters.get('force_source')
self.force_absent = parameters.get('force_absent')
self.force_tag = parameters.get('force_tag')
self.load_path = parameters.get('load_path')
self.name = parameters.get('name')
self.network = build.get('network')
self.extra_hosts = clean_dict_booleans_for_docker_api(build.get('etc_hosts'))
self.nocache = build.get('nocache', False)
self.build_path = build.get('path')
self.pull = build.get('pull')
self.target = build.get('target')
self.repository = parameters.get('repository')
self.rm = build.get('rm', True)
self.state = parameters.get('state')
self.tag = parameters.get('tag')
self.http_timeout = build.get('http_timeout')
self.push = parameters.get('push')
self.buildargs = build.get('args')
self.use_config_proxy = build.get('use_config_proxy')
# If name contains a tag, it takes precedence over tag parameter.
if not is_image_name_id(self.name):
repo, repo_tag = parse_repository_tag(self.name)
if repo_tag:
self.name = repo
self.tag = repo_tag
if self.state == 'present':
self.present()
elif self.state == 'absent':
self.absent()
def fail(self, msg):
self.client.fail(msg)
def present(self):
'''
Handles state = 'present', which includes building, loading or pulling an image,
depending on user provided parameters.
:returns None
'''
image = self.client.find_image(name=self.name, tag=self.tag)
if not image or self.force_source:
if self.source == 'build':
# Build the image
if not os.path.isdir(self.build_path):
self.fail("Requested build path %s could not be found or you do not have access." % self.build_path)
image_name = self.name
if self.tag:
image_name = "%s:%s" % (self.name, self.tag)
self.log("Building image %s" % image_name)
self.results['actions'].append("Built image %s from %s" % (image_name, self.build_path))
self.results['changed'] = True
if not self.check_mode:
self.results.update(self.build_image())
elif self.source == 'load':
# Load the image from an archive
if not os.path.isfile(self.load_path):
self.fail("Error loading image %s. Specified path %s does not exist." % (self.name,
self.load_path))
image_name = self.name
if self.tag:
image_name = "%s:%s" % (self.name, self.tag)
self.results['actions'].append("Loaded image %s from %s" % (image_name, self.load_path))
self.results['changed'] = True
if not self.check_mode:
self.results['image'] = self.load_image()
elif self.source == 'pull':
# pull the image
self.results['actions'].append('Pulled image %s:%s' % (self.name, self.tag))
self.results['changed'] = True
if not self.check_mode:
self.results['image'], dummy = self.client.pull_image(self.name, tag=self.tag)
elif self.source == 'local':
if image is None:
name = self.name
if self.tag:
name = "%s:%s" % (self.name, self.tag)
self.client.fail('Cannot find the image %s locally.' % name)
if not self.check_mode and image and image['Id'] == self.results['image']['Id']:
self.results['changed'] = False
if self.archive_path:
self.archive_image(self.name, self.tag)
if self.push and not self.repository:
self.push_image(self.name, self.tag)
elif self.repository:
self.tag_image(self.name, self.tag, self.repository, push=self.push)
def absent(self):
'''
Handles state = 'absent', which removes an image.
:return None
'''
name = self.name
if is_image_name_id(name):
image = self.client.find_image_by_id(name)
else:
image = self.client.find_image(name, self.tag)
if self.tag:
name = "%s:%s" % (self.name, self.tag)
if image:
if not self.check_mode:
try:
self.client.remove_image(name, force=self.force_absent)
except Exception as exc:
self.fail("Error removing image %s - %s" % (name, str(exc)))
self.results['changed'] = True
self.results['actions'].append("Removed image %s" % (name))
self.results['image']['state'] = 'Deleted'
def archive_image(self, name, tag):
'''
Archive an image to a .tar file. Called when archive_path is passed.
:param name - name of the image. Type: str
:return None
'''
if not tag:
tag = "latest"
image = self.client.find_image(name=name, tag=tag)
if not image:
self.log("archive image: image %s:%s not found" % (name, tag))
return
image_name = "%s:%s" % (name, tag)
self.results['actions'].append('Archived image %s to %s' % (image_name, self.archive_path))
self.results['changed'] = True
if not self.check_mode:
self.log("Getting archive of image %s" % image_name)
try:
image = self.client.get_image(image_name)
except Exception as exc:
self.fail("Error getting image %s - %s" % (image_name, str(exc)))
try:
with open(self.archive_path, 'wb') as fd:
if self.client.docker_py_version >= LooseVersion('3.0.0'):
for chunk in image:
fd.write(chunk)
else:
for chunk in image.stream(2048, decode_content=False):
fd.write(chunk)
except Exception as exc:
self.fail("Error writing image archive %s - %s" % (self.archive_path, str(exc)))
image = self.client.find_image(name=name, tag=tag)
if image:
self.results['image'] = image
def push_image(self, name, tag=None):
'''
If the name of the image contains a repository path, then push the image.
:param name Name of the image to push.
:param tag Use a specific tag.
:return: None
'''
repository = name
if not tag:
repository, tag = parse_repository_tag(name)
registry, repo_name = resolve_repository_name(repository)
self.log("push %s to %s/%s:%s" % (self.name, registry, repo_name, tag))
if registry:
self.results['actions'].append("Pushed image %s to %s/%s:%s" % (self.name, registry, repo_name, tag))
self.results['changed'] = True
if not self.check_mode:
status = None
try:
changed = False
for line in self.client.push(repository, tag=tag, stream=True, decode=True):
self.log(line, pretty_print=True)
if line.get('errorDetail'):
raise Exception(line['errorDetail']['message'])
status = line.get('status')
if status == 'Pushing':
changed = True
self.results['changed'] = changed
except Exception as exc:
if re.search('unauthorized', str(exc)):
if re.search('authentication required', str(exc)):
self.fail("Error pushing image %s/%s:%s - %s. Try logging into %s first." %
(registry, repo_name, tag, str(exc), registry))
else:
self.fail("Error pushing image %s/%s:%s - %s. Does the repository exist?" %
(registry, repo_name, tag, str(exc)))
self.fail("Error pushing image %s: %s" % (repository, str(exc)))
self.results['image'] = self.client.find_image(name=repository, tag=tag)
if not self.results['image']:
self.results['image'] = dict()
self.results['image']['push_status'] = status
def tag_image(self, name, tag, repository, push=False):
'''
Tag an image into a repository.
:param name: name of the image. required.
:param tag: image tag.
:param repository: path to the repository. required.
:param push: bool. push the image once it's tagged.
:return: None
'''
repo, repo_tag = parse_repository_tag(repository)
if not repo_tag:
repo_tag = "latest"
if tag:
repo_tag = tag
image = self.client.find_image(name=repo, tag=repo_tag)
found = 'found' if image else 'not found'
self.log("image %s was %s" % (repo, found))
if not image or self.force_tag:
self.log("tagging %s:%s to %s:%s" % (name, tag, repo, repo_tag))
self.results['changed'] = True
self.results['actions'].append("Tagged image %s:%s to %s:%s" % (name, tag, repo, repo_tag))
if not self.check_mode:
try:
# Finding the image does not always work, especially running a localhost registry. In those
# cases, if we don't set force=True, it errors.
image_name = name
if tag and not re.search(tag, name):
image_name = "%s:%s" % (name, tag)
tag_status = self.client.tag(image_name, repo, tag=repo_tag, force=True)
if not tag_status:
raise Exception("Tag operation failed.")
except Exception as exc:
self.fail("Error: failed to tag image - %s" % str(exc))
self.results['image'] = self.client.find_image(name=repo, tag=repo_tag)
if image and image['Id'] == self.results['image']['Id']:
self.results['changed'] = False
if push:
self.push_image(repo, repo_tag)
def build_image(self):
'''
Build an image
:return: image dict
'''
params = dict(
path=self.build_path,
tag=self.name,
rm=self.rm,
nocache=self.nocache,
timeout=self.http_timeout,
pull=self.pull,
forcerm=self.rm,
dockerfile=self.dockerfile,
decode=True,
)
if self.client.docker_py_version < LooseVersion('3.0.0'):
params['stream'] = True
if self.tag:
params['tag'] = "%s:%s" % (self.name, self.tag)
if self.container_limits:
params['container_limits'] = self.container_limits
if self.buildargs:
for key, value in self.buildargs.items():
self.buildargs[key] = to_native(value)
params['buildargs'] = self.buildargs
if self.cache_from:
params['cache_from'] = self.cache_from
if self.network:
params['network_mode'] = self.network
if self.extra_hosts:
params['extra_hosts'] = self.extra_hosts
if self.use_config_proxy:
params['use_config_proxy'] = self.use_config_proxy
# Due to a bug in docker-py, it will crash if
# use_config_proxy is True and buildargs is None
if 'buildargs' not in params:
params['buildargs'] = {}
if self.target:
params['target'] = self.target
build_output = []
for line in self.client.build(**params):
# line = json.loads(line)
self.log(line, pretty_print=True)
if "stream" in line or "status" in line:
build_line = line.get("stream") or line.get("status")
build_output.append(build_line)
if line.get('error'):
if line.get('errorDetail'):
errorDetail = line.get('errorDetail')
self.fail(
"Error building %s - code: %s, message: %s, logs: %s" % (
self.name,
errorDetail.get('code'),
errorDetail.get('message'),
build_output))
else:
self.fail("Error building %s - message: %s, logs: %s" % (
self.name, line.get('error'), build_output))
return {"stdout": "\n".join(build_output),
"image": self.client.find_image(name=self.name, tag=self.tag)}
def load_image(self):
'''
Load an image from a .tar archive
:return: image dict
'''
try:
self.log("Opening image %s" % self.load_path)
with open(self.load_path, 'rb') as image_tar:
self.log("Loading image from %s" % self.load_path)
self.client.load_image(image_tar)
except EnvironmentError as exc:
if exc.errno == errno.ENOENT:
self.fail("Error opening image %s - %s" % (self.load_path, str(exc)))
self.fail("Error loading image %s - %s" % (self.name, str(exc)))
except Exception as exc:
self.fail("Error loading image %s - %s" % (self.name, str(exc)))
return self.client.find_image(self.name, self.tag)
def main():
argument_spec = dict(
source=dict(type='str', choices=['build', 'load', 'pull', 'local']),
build=dict(type='dict', options=dict(
cache_from=dict(type='list', elements='str'),
container_limits=dict(type='dict', options=dict(
memory=dict(type='int'),
memswap=dict(type='int'),
cpushares=dict(type='int'),
cpusetcpus=dict(type='str'),
)),
dockerfile=dict(type='str'),
http_timeout=dict(type='int'),
network=dict(type='str'),
nocache=dict(type='bool', default=False),
path=dict(type='path', required=True),
pull=dict(type='bool'),
rm=dict(type='bool', default=True),
args=dict(type='dict'),
use_config_proxy=dict(type='bool'),
target=dict(type='str'),
etc_hosts=dict(type='dict'),
)),
archive_path=dict(type='path'),
container_limits=dict(type='dict', options=dict(
memory=dict(type='int'),
memswap=dict(type='int'),
cpushares=dict(type='int'),
cpusetcpus=dict(type='str'),
), removed_in_version='2.0.0', removed_from_collection='community.general'), # was Ansible 2.12
dockerfile=dict(type='str', removed_in_version='2.0.0', removed_from_collection='community.general'), # was Ansible 2.12
force=dict(type='bool', removed_in_version='2.0.0', removed_from_collection='community.general'), # was Ansible 2.12
force_source=dict(type='bool', default=False),
force_absent=dict(type='bool', default=False),
force_tag=dict(type='bool', default=False),
http_timeout=dict(type='int', removed_in_version='2.0.0', removed_from_collection='community.general'), # was Ansible 2.12
load_path=dict(type='path'),
name=dict(type='str', required=True),
nocache=dict(type='bool', default=False, removed_in_version='2.0.0', removed_from_collection='community.general'), # was Ansible 2.12
path=dict(type='path', aliases=['build_path'], removed_in_version='2.0.0', removed_from_collection='community.general'), # was Ansible 2.12
pull=dict(type='bool', removed_in_version='2.0.0', removed_from_collection='community.general'), # was Ansible 2.12
push=dict(type='bool', default=False),
repository=dict(type='str'),
rm=dict(type='bool', default=True, removed_in_version='2.0.0', removed_from_collection='community.general'), # was Ansible 2.12
state=dict(type='str', default='present', choices=['absent', 'present', 'build']),
tag=dict(type='str', default='latest'),
use_tls=dict(type='str', choices=['no', 'encrypt', 'verify'], removed_in_version='2.0.0',
removed_from_collection='community.general'), # was Ansible 2.12
buildargs=dict(type='dict', removed_in_version='2.0.0', removed_from_collection='community.general'), # was Ansible 2.12
)
required_if = [
# ('state', 'present', ['source']), -- enable in community.general 2.0.0
# ('source', 'build', ['build']), -- enable in community.general 2.0.0
('source', 'load', ['load_path']),
]
def detect_build_cache_from(client):
return client.module.params['build'] and client.module.params['build'].get('cache_from') is not None
def detect_build_network(client):
return client.module.params['build'] and client.module.params['build'].get('network') is not None
def detect_build_target(client):
return client.module.params['build'] and client.module.params['build'].get('target') is not None
def detect_use_config_proxy(client):
return client.module.params['build'] and client.module.params['build'].get('use_config_proxy') is not None
def detect_etc_hosts(client):
return client.module.params['build'] and bool(client.module.params['build'].get('etc_hosts'))
option_minimal_versions = dict()
option_minimal_versions["build.cache_from"] = dict(docker_py_version='2.1.0', docker_api_version='1.25', detect_usage=detect_build_cache_from)
option_minimal_versions["build.network"] = dict(docker_py_version='2.4.0', docker_api_version='1.25', detect_usage=detect_build_network)
option_minimal_versions["build.target"] = dict(docker_py_version='2.4.0', detect_usage=detect_build_target)
option_minimal_versions["build.use_config_proxy"] = dict(docker_py_version='3.7.0', detect_usage=detect_use_config_proxy)
option_minimal_versions["build.etc_hosts"] = dict(docker_py_version='2.6.0', docker_api_version='1.27', detect_usage=detect_etc_hosts)
client = AnsibleDockerClient(
argument_spec=argument_spec,
required_if=required_if,
supports_check_mode=True,
min_docker_version='1.8.0',
min_docker_api_version='1.20',
option_minimal_versions=option_minimal_versions,
)
if client.module.params['state'] == 'build':
client.module.deprecate('The "build" state has been deprecated for a long time. '
'Please use "present", which has the same meaning as "build".',
version='2.0.0', collection_name='community.general') # was Ansible 2.11
client.module.params['state'] = 'present'
if client.module.params['use_tls']:
client.module.deprecate('The "use_tls" option has been deprecated for a long time. '
'Please use the "tls" and "validate_certs" options instead.',
version='2.0.0', collection_name='community.general') # was Ansible 2.11
if not is_valid_tag(client.module.params['tag'], allow_empty=True):
client.fail('"{0}" is not a valid docker tag!'.format(client.module.params['tag']))
build_options = dict(
container_limits='container_limits',
dockerfile='dockerfile',
http_timeout='http_timeout',
nocache='nocache',
path='path',
pull='pull',
rm='rm',
buildargs='args',
)
for option, build_option in build_options.items():
default_value = None
if option in ('rm', ):
default_value = True
elif option in ('nocache', ):
default_value = False
if client.module.params[option] != default_value:
if client.module.params['build'] is None:
client.module.params['build'] = dict()
if client.module.params['build'].get(build_option, default_value) != default_value:
client.fail('Cannot specify both %s and build.%s!' % (option, build_option))
client.module.params['build'][build_option] = client.module.params[option]
client.module.deprecate('Please specify build.%s instead of %s. The %s option '
'has been renamed' % (build_option, option, option),
version='2.0.0', collection_name='community.general') # was Ansible 2.12
if client.module.params['source'] == 'build':
if (not client.module.params['build'] or not client.module.params['build'].get('path')):
client.fail('If "source" is set to "build", the "build.path" option must be specified.')
if client.module.params['build'].get('pull') is None:
client.module.deprecate("The default for build.pull is currently 'yes', but will be changed to "
"'no' in community.general 2.0.0. Please set build.pull explicitly to the value you need",
version='2.0.0', collection_name='community.general') # was Ansible 2.12
client.module.params['build']['pull'] = True # TODO: change to False in community.general 2.0.0
if client.module.params['state'] == 'present' and client.module.params['source'] is None:
# Autodetection. To be removed in community.general 2.0.0.
if (client.module.params['build'] or dict()).get('path'):
client.module.params['source'] = 'build'
elif client.module.params['load_path']:
client.module.params['source'] = 'load'
else:
client.module.params['source'] = 'pull'
client.module.deprecate('The value of the "source" option was determined to be "%s". '
'Please set the "source" option explicitly. Autodetection will '
'be removed in community.general 2.0.0.' % client.module.params['source'],
version='2.0.0', collection_name='community.general') # was Ansible 2.12
if client.module.params['force']:
client.module.params['force_source'] = True
client.module.params['force_absent'] = True
client.module.params['force_tag'] = True
client.module.deprecate('The "force" option will be removed in community.general 2.0.0. Please '
'use the "force_source", "force_absent" or "force_tag" option '
'instead, depending on what you want to force.',
version='2.0.0', collection_name='community.general') # was Ansible 2.12
try:
results = dict(
changed=False,
actions=[],
image={}
)
ImageManager(client, results)
client.module.exit_json(**results)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
except RequestException as e:
client.fail('An unexpected requests error occurred when docker-py tried to talk to the docker daemon: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':
main()

View file

@ -1 +0,0 @@
docker_image_info.py

View file

@ -1,270 +0,0 @@
#!/usr/bin/python
#
# Copyright 2016 Red Hat | Ansible
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
---
module: docker_image_info
short_description: Inspect docker images
description:
- Provide one or more image names, and the module will inspect each, returning an array of inspection results.
- If an image does not exist locally, it will not appear in the results. If you want to check whether an image exists
locally, you can call the module with the image name, then check whether the result list is empty (image does not
exist) or has one element (the image exists locally).
- The module will not attempt to pull images from registries. Use M(community.general.docker_image) with I(source) set to C(pull)
to ensure an image is pulled.
notes:
- This module was called C(docker_image_facts) before Ansible 2.8. The usage did not change.
options:
name:
description:
- An image name or a list of image names. Name format will be C(name[:tag]) or C(repository/name[:tag]),
where C(tag) is optional. If a tag is not provided, C(latest) will be used. Instead of image names, also
image IDs can be used.
- If no name is provided, a list of all images will be returned.
type: list
elements: str
extends_documentation_fragment:
- community.general.docker
- community.general.docker.docker_py_1_documentation
requirements:
- "L(Docker SDK for Python,https://docker-py.readthedocs.io/en/stable/) >= 1.8.0 (use L(docker-py,https://pypi.org/project/docker-py/) for Python 2.6)"
- "Docker API >= 1.20"
author:
- Chris Houseknecht (@chouseknecht)
'''
EXAMPLES = '''
- name: Inspect a single image
community.general.docker_image_info:
name: pacur/centos-7
- name: Inspect multiple images
community.general.docker_image_info:
name:
- pacur/centos-7
- sinatra
register: result
- name: Make sure that both images pacur/centos-7 and sinatra exist locally
ansible.builtin.assert:
that:
- result.images | length == 2
'''
RETURN = '''
images:
description:
- Inspection results for the selected images.
- The list only contains inspection results of images existing locally.
returned: always
type: list
elements: dict
sample: [
{
"Architecture": "amd64",
"Author": "",
"Comment": "",
"Config": {
"AttachStderr": false,
"AttachStdin": false,
"AttachStdout": false,
"Cmd": [
"/etc/docker/registry/config.yml"
],
"Domainname": "",
"Entrypoint": [
"/bin/registry"
],
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
"ExposedPorts": {
"5000/tcp": {}
},
"Hostname": "e5c68db50333",
"Image": "c72dce2618dc8f7b794d2b2c2b1e64e0205ead5befc294f8111da23bd6a2c799",
"Labels": {},
"OnBuild": [],
"OpenStdin": false,
"StdinOnce": false,
"Tty": false,
"User": "",
"Volumes": {
"/var/lib/registry": {}
},
"WorkingDir": ""
},
"Container": "e83a452b8fb89d78a25a6739457050131ca5c863629a47639530d9ad2008d610",
"ContainerConfig": {
"AttachStderr": false,
"AttachStdin": false,
"AttachStdout": false,
"Cmd": [
"/bin/sh",
"-c",
'#(nop) CMD ["/etc/docker/registry/config.yml"]'
],
"Domainname": "",
"Entrypoint": [
"/bin/registry"
],
"Env": [
"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin"
],
"ExposedPorts": {
"5000/tcp": {}
},
"Hostname": "e5c68db50333",
"Image": "c72dce2618dc8f7b794d2b2c2b1e64e0205ead5befc294f8111da23bd6a2c799",
"Labels": {},
"OnBuild": [],
"OpenStdin": false,
"StdinOnce": false,
"Tty": false,
"User": "",
"Volumes": {
"/var/lib/registry": {}
},
"WorkingDir": ""
},
"Created": "2016-03-08T21:08:15.399680378Z",
"DockerVersion": "1.9.1",
"GraphDriver": {
"Data": null,
"Name": "aufs"
},
"Id": "53773d8552f07b730f3e19979e32499519807d67b344141d965463a950a66e08",
"Name": "registry:2",
"Os": "linux",
"Parent": "f0b1f729f784b755e7bf9c8c2e65d8a0a35a533769c2588f02895f6781ac0805",
"RepoDigests": [],
"RepoTags": [
"registry:2"
],
"Size": 0,
"VirtualSize": 165808884
}
]
'''
import traceback
try:
from docker import utils
from docker.errors import DockerException
except ImportError:
# missing Docker SDK for Python handled in ansible.module_utils.docker.common
pass
from ansible_collections.community.general.plugins.module_utils.docker.common import (
AnsibleDockerClient,
DockerBaseClass,
is_image_name_id,
RequestException,
)
class ImageManager(DockerBaseClass):
def __init__(self, client, results):
super(ImageManager, self).__init__()
self.client = client
self.results = results
self.name = self.client.module.params.get('name')
self.log("Gathering facts for images: %s" % (str(self.name)))
if self.name:
self.results['images'] = self.get_facts()
else:
self.results['images'] = self.get_all_images()
def fail(self, msg):
self.client.fail(msg)
def get_facts(self):
'''
Lookup and inspect each image name found in the names parameter.
:returns array of image dictionaries
'''
results = []
names = self.name
if not isinstance(names, list):
names = [names]
for name in names:
if is_image_name_id(name):
self.log('Fetching image %s (ID)' % (name))
image = self.client.find_image_by_id(name)
else:
repository, tag = utils.parse_repository_tag(name)
if not tag:
tag = 'latest'
self.log('Fetching image %s:%s' % (repository, tag))
image = self.client.find_image(name=repository, tag=tag)
if image:
results.append(image)
return results
def get_all_images(self):
results = []
images = self.client.images()
for image in images:
try:
inspection = self.client.inspect_image(image['Id'])
except Exception as exc:
self.fail("Error inspecting image %s - %s" % (image['Id'], str(exc)))
results.append(inspection)
return results
def main():
argument_spec = dict(
name=dict(type='list', elements='str'),
)
client = AnsibleDockerClient(
argument_spec=argument_spec,
supports_check_mode=True,
min_docker_api_version='1.20',
)
if client.module._name in ('docker_image_facts', 'community.general.docker_image_facts'):
client.module.deprecate("The 'docker_image_facts' module has been renamed to 'docker_image_info'",
version='2.0.0', collection_name='community.general') # was Ansible 2.12
try:
results = dict(
changed=False,
images=[]
)
ImageManager(client, results)
client.module.exit_json(**results)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
except RequestException as e:
client.fail('An unexpected requests error occurred when docker-py tried to talk to the docker daemon: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':
main()

View file

@ -1,485 +0,0 @@
#!/usr/bin/python
#
# (c) 2016 Olaf Kilian <olaf.kilian@symanex.com>
# Chris Houseknecht, <house@redhat.com>
# James Tanner, <jtanner@redhat.com>
#
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
---
module: docker_login
short_description: Log into a Docker registry.
description:
- Provides functionality similar to the "docker login" command.
- Authenticate with a docker registry and add the credentials to your local Docker config file respectively the
credentials store associated to the registry. Adding the credentials to the config files resp. the credential
store allows future connections to the registry using tools such as Ansible's Docker modules, the Docker CLI
and Docker SDK for Python without needing to provide credentials.
- Running in check mode will perform the authentication without updating the config file.
options:
registry_url:
description:
- The registry URL.
type: str
default: "https://index.docker.io/v1/"
aliases:
- registry
- url
username:
description:
- The username for the registry account.
- Required when I(state) is C(present).
type: str
password:
description:
- The plaintext password for the registry account.
- Required when I(state) is C(present).
type: str
email:
description:
- Does nothing, do not use.
- Will be removed in community.general 3.0.0.
type: str
reauthorize:
description:
- Refresh existing authentication found in the configuration file.
type: bool
default: no
aliases:
- reauth
config_path:
description:
- Custom path to the Docker CLI configuration file.
type: path
default: ~/.docker/config.json
aliases:
- dockercfg_path
state:
description:
- This controls the current state of the user. C(present) will login in a user, C(absent) will log them out.
- To logout you only need the registry server, which defaults to DockerHub.
- Before 2.1 you could ONLY log in.
- Docker does not support 'logout' with a custom config file.
type: str
default: 'present'
choices: ['present', 'absent']
extends_documentation_fragment:
- community.general.docker
- community.general.docker.docker_py_1_documentation
requirements:
- "L(Docker SDK for Python,https://docker-py.readthedocs.io/en/stable/) >= 1.8.0 (use L(docker-py,https://pypi.org/project/docker-py/) for Python 2.6)"
- "L(Python bindings for docker credentials store API) >= 0.2.1
(use L(docker-pycreds,https://pypi.org/project/docker-pycreds/) when using Docker SDK for Python < 4.0.0)"
- "Docker API >= 1.20"
author:
- Olaf Kilian (@olsaki) <olaf.kilian@symanex.com>
- Chris Houseknecht (@chouseknecht)
'''
EXAMPLES = '''
- name: Log into DockerHub
community.general.docker_login:
username: docker
password: rekcod
- name: Log into private registry and force re-authorization
community.general.docker_login:
registry_url: your.private.registry.io
username: yourself
password: secrets3
reauthorize: yes
- name: Log into DockerHub using a custom config file
community.general.docker_login:
username: docker
password: rekcod
config_path: /tmp/.mydockercfg
- name: Log out of DockerHub
community.general.docker_login:
state: absent
'''
RETURN = '''
login_results:
description: Results from the login.
returned: when state='present'
type: dict
sample: {
"serveraddress": "localhost:5000",
"username": "testuser"
}
'''
import base64
import json
import os
import re
import traceback
from ansible.module_utils._text import to_bytes, to_text
try:
from docker.errors import DockerException
from docker import auth
# Earlier versions of docker/docker-py put decode_auth
# in docker.auth.auth instead of docker.auth
if hasattr(auth, 'decode_auth'):
from docker.auth import decode_auth
else:
from docker.auth.auth import decode_auth
except ImportError:
# missing Docker SDK for Python handled in ansible.module_utils.docker.common
pass
from ansible_collections.community.general.plugins.module_utils.docker.common import (
AnsibleDockerClient,
HAS_DOCKER_PY,
DEFAULT_DOCKER_REGISTRY,
DockerBaseClass,
EMAIL_REGEX,
RequestException,
)
NEEDS_DOCKER_PYCREDS = False
# Early versions of docker/docker-py rely on docker-pycreds for
# the credential store api.
if HAS_DOCKER_PY:
try:
from docker.credentials.errors import StoreError, CredentialsNotFound
from docker.credentials import Store
except ImportError:
try:
from dockerpycreds.errors import StoreError, CredentialsNotFound
from dockerpycreds.store import Store
except ImportError as exc:
HAS_DOCKER_ERROR = str(exc)
NEEDS_DOCKER_PYCREDS = True
if NEEDS_DOCKER_PYCREDS:
# docker-pycreds missing, so we need to create some place holder classes
# to allow instantiation.
class StoreError(Exception):
pass
class CredentialsNotFound(Exception):
pass
class DockerFileStore(object):
'''
A custom credential store class that implements only the functionality we need to
update the docker config file when no credential helpers is provided.
'''
program = "<legacy config>"
def __init__(self, config_path):
self._config_path = config_path
# Make sure we have a minimal config if none is available.
self._config = dict(
auths=dict()
)
try:
# Attempt to read the existing config.
with open(self._config_path, "r") as f:
config = json.load(f)
except (ValueError, IOError):
# No config found or an invalid config found so we'll ignore it.
config = dict()
# Update our internal config with what ever was loaded.
self._config.update(config)
@property
def config_path(self):
'''
Return the config path configured in this DockerFileStore instance.
'''
return self._config_path
def get(self, server):
'''
Retrieve credentials for `server` if there are any in the config file.
Otherwise raise a `StoreError`
'''
server_creds = self._config['auths'].get(server)
if not server_creds:
raise CredentialsNotFound('No matching credentials')
(username, password) = decode_auth(server_creds['auth'])
return dict(
Username=username,
Secret=password
)
def _write(self):
'''
Write config back out to disk.
'''
# Make sure directory exists
dir = os.path.dirname(self._config_path)
if not os.path.exists(dir):
os.makedirs(dir)
# Write config; make sure it has permissions 0x600
content = json.dumps(self._config, indent=4, sort_keys=True).encode('utf-8')
f = os.open(self._config_path, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
try:
os.write(f, content)
finally:
os.close(f)
def store(self, server, username, password):
'''
Add a credentials for `server` to the current configuration.
'''
b64auth = base64.b64encode(
to_bytes(username) + b':' + to_bytes(password)
)
auth = to_text(b64auth)
# build up the auth structure
if 'auths' not in self._config:
self._config['auths'] = dict()
self._config['auths'][server] = dict(
auth=auth
)
self._write()
def erase(self, server):
'''
Remove credentials for the given server from the configuration.
'''
if 'auths' in self._config and server in self._config['auths']:
self._config['auths'].pop(server)
self._write()
class LoginManager(DockerBaseClass):
def __init__(self, client, results):
super(LoginManager, self).__init__()
self.client = client
self.results = results
parameters = self.client.module.params
self.check_mode = self.client.check_mode
self.registry_url = parameters.get('registry_url')
self.username = parameters.get('username')
self.password = parameters.get('password')
self.email = parameters.get('email')
self.reauthorize = parameters.get('reauthorize')
self.config_path = parameters.get('config_path')
self.state = parameters.get('state')
def run(self):
'''
Do the actuall work of this task here. This allows instantiation for partial
testing.
'''
if self.state == 'present':
self.login()
else:
self.logout()
def fail(self, msg):
self.client.fail(msg)
def login(self):
'''
Log into the registry with provided username/password. On success update the config
file with the new authorization.
:return: None
'''
if self.email and not re.match(EMAIL_REGEX, self.email):
self.fail("Parameter error: the email address appears to be incorrect. Expecting it to match "
"/%s/" % (EMAIL_REGEX))
self.results['actions'].append("Logged into %s" % (self.registry_url))
self.log("Log into %s with username %s" % (self.registry_url, self.username))
try:
response = self.client.login(
self.username,
password=self.password,
email=self.email,
registry=self.registry_url,
reauth=self.reauthorize,
dockercfg_path=self.config_path
)
except Exception as exc:
self.fail("Logging into %s for user %s failed - %s" % (self.registry_url, self.username, str(exc)))
# If user is already logged in, then response contains password for user
if 'password' in response:
# This returns correct password if user is logged in and wrong password is given.
# So if it returns another password as we passed, and the user didn't request to
# reauthorize, still do it.
if not self.reauthorize and response['password'] != self.password:
try:
response = self.client.login(
self.username,
password=self.password,
email=self.email,
registry=self.registry_url,
reauth=True,
dockercfg_path=self.config_path
)
except Exception as exc:
self.fail("Logging into %s for user %s failed - %s" % (self.registry_url, self.username, str(exc)))
response.pop('password', None)
self.results['login_result'] = response
self.update_credentials()
def logout(self):
'''
Log out of the registry. On success update the config file.
:return: None
'''
# Get the configuration store.
store = self.get_credential_store_instance(self.registry_url, self.config_path)
try:
current = store.get(self.registry_url)
except CredentialsNotFound:
# get raises an exception on not found.
self.log("Credentials for %s not present, doing nothing." % (self.registry_url))
self.results['changed'] = False
return
if not self.check_mode:
store.erase(self.registry_url)
self.results['changed'] = True
def update_credentials(self):
'''
If the authorization is not stored attempt to store authorization values via
the appropriate credential helper or to the config file.
:return: None
'''
# Check to see if credentials already exist.
store = self.get_credential_store_instance(self.registry_url, self.config_path)
try:
current = store.get(self.registry_url)
except CredentialsNotFound:
# get raises an exception on not found.
current = dict(
Username='',
Secret=''
)
if current['Username'] != self.username or current['Secret'] != self.password or self.reauthorize:
if not self.check_mode:
store.store(self.registry_url, self.username, self.password)
self.log("Writing credentials to configured helper %s for %s" % (store.program, self.registry_url))
self.results['actions'].append("Wrote credentials to configured helper %s for %s" % (
store.program, self.registry_url))
self.results['changed'] = True
def get_credential_store_instance(self, registry, dockercfg_path):
'''
Return an instance of docker.credentials.Store used by the given registry.
:return: A Store or None
:rtype: Union[docker.credentials.Store, NoneType]
'''
# Older versions of docker-py don't have this feature.
try:
credstore_env = self.client.credstore_env
except AttributeError:
credstore_env = None
config = auth.load_config(config_path=dockercfg_path)
if hasattr(auth, 'get_credential_store'):
store_name = auth.get_credential_store(config, registry)
elif 'credsStore' in config:
store_name = config['credsStore']
else:
store_name = None
# Make sure that there is a credential helper before trying to instantiate a
# Store object.
if store_name:
self.log("Found credential store %s" % store_name)
return Store(store_name, environment=credstore_env)
return DockerFileStore(dockercfg_path)
def main():
argument_spec = dict(
registry_url=dict(type='str', default=DEFAULT_DOCKER_REGISTRY, aliases=['registry', 'url']),
username=dict(type='str'),
password=dict(type='str', no_log=True),
email=dict(type='str', removed_in_version='3.0.0', removed_from_collection='community.general'), # was Ansible 2.14
reauthorize=dict(type='bool', default=False, aliases=['reauth']),
state=dict(type='str', default='present', choices=['present', 'absent']),
config_path=dict(type='path', default='~/.docker/config.json', aliases=['dockercfg_path']),
)
required_if = [
('state', 'present', ['username', 'password']),
]
client = AnsibleDockerClient(
argument_spec=argument_spec,
supports_check_mode=True,
required_if=required_if,
min_docker_api_version='1.20',
)
try:
results = dict(
changed=False,
actions=[],
login_result={}
)
manager = LoginManager(client, results)
manager.run()
if 'actions' in results:
del results['actions']
client.module.exit_json(**results)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
except RequestException as e:
client.fail('An unexpected requests error occurred when docker-py tried to talk to the docker daemon: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':
main()

View file

@ -1,717 +0,0 @@
#!/usr/bin/python
#
# Copyright 2016 Red Hat | Ansible
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
module: docker_network
short_description: Manage Docker networks
description:
- Create/remove Docker networks and connect containers to them.
- Performs largely the same function as the "docker network" CLI subcommand.
options:
name:
description:
- Name of the network to operate on.
type: str
required: yes
aliases:
- network_name
connected:
description:
- List of container names or container IDs to connect to a network.
- Please note that the module only makes sure that these containers are connected to the network,
but does not care about connection options. If you rely on specific IP addresses etc., use the
M(community.general.docker_container) module to ensure your containers are correctly connected to this network.
type: list
elements: str
aliases:
- containers
driver:
description:
- Specify the type of network. Docker provides bridge and overlay drivers, but 3rd party drivers can also be used.
type: str
default: bridge
driver_options:
description:
- Dictionary of network settings. Consult docker docs for valid options and values.
type: dict
force:
description:
- With state C(absent) forces disconnecting all containers from the
network prior to deleting the network. With state C(present) will
disconnect all containers, delete the network and re-create the
network.
- This option is required if you have changed the IPAM or driver options
and want an existing network to be updated to use the new options.
type: bool
default: no
appends:
description:
- By default the connected list is canonical, meaning containers not on the list are removed from the network.
- Use I(appends) to leave existing containers connected.
type: bool
default: no
aliases:
- incremental
enable_ipv6:
description:
- Enable IPv6 networking.
type: bool
ipam_driver:
description:
- Specify an IPAM driver.
type: str
ipam_driver_options:
description:
- Dictionary of IPAM driver options.
type: dict
ipam_options:
description:
- Dictionary of IPAM options.
- Deprecated in 2.8, will be removed in community.general 2.0.0. Use parameter I(ipam_config) instead. In Docker 1.10.0, IPAM
options were introduced (see L(here,https://github.com/moby/moby/pull/17316)). This module parameter addresses
the IPAM config not the newly introduced IPAM options. For the IPAM options, see the I(ipam_driver_options)
parameter.
type: dict
suboptions:
subnet:
description:
- IP subset in CIDR notation.
type: str
iprange:
description:
- IP address range in CIDR notation.
type: str
gateway:
description:
- IP gateway address.
type: str
aux_addresses:
description:
- Auxiliary IP addresses used by Network driver, as a mapping from hostname to IP.
type: dict
ipam_config:
description:
- List of IPAM config blocks. Consult
L(Docker docs,https://docs.docker.com/compose/compose-file/compose-file-v2/#ipam) for valid options and values.
Note that I(iprange) is spelled differently here (we use the notation from the Docker SDK for Python).
type: list
elements: dict
suboptions:
subnet:
description:
- IP subset in CIDR notation.
type: str
iprange:
description:
- IP address range in CIDR notation.
type: str
gateway:
description:
- IP gateway address.
type: str
aux_addresses:
description:
- Auxiliary IP addresses used by Network driver, as a mapping from hostname to IP.
type: dict
state:
description:
- C(absent) deletes the network. If a network has connected containers, it
cannot be deleted. Use the I(force) option to disconnect all containers
and delete the network.
- C(present) creates the network, if it does not already exist with the
specified parameters, and connects the list of containers provided via
the connected parameter. Containers not on the list will be disconnected.
An empty list will leave no containers connected to the network. Use the
I(appends) option to leave existing containers connected. Use the I(force)
options to force re-creation of the network.
type: str
default: present
choices:
- absent
- present
internal:
description:
- Restrict external access to the network.
type: bool
labels:
description:
- Dictionary of labels.
type: dict
scope:
description:
- Specify the network's scope.
type: str
choices:
- local
- global
- swarm
attachable:
description:
- If enabled, and the network is in the global scope, non-service containers on worker nodes will be able to connect to the network.
type: bool
extends_documentation_fragment:
- community.general.docker
- community.general.docker.docker_py_1_documentation
notes:
- When network options are changed, the module disconnects all containers from the network, deletes the network, and re-creates the network.
It does not try to reconnect containers, except the ones listed in (I(connected), and even for these, it does not consider specific
connection options like fixed IP addresses or MAC addresses. If you need more control over how the containers are connected to the
network, loop the M(community.general.docker_container) module to loop over your containers to make sure they are connected properly.
- The module does not support Docker Swarm, i.e. it will not try to disconnect or reconnect services. If services are connected to the
network, deleting the network will fail. When network options are changed, the network has to be deleted and recreated, so this will
fail as well.
author:
- "Ben Keith (@keitwb)"
- "Chris Houseknecht (@chouseknecht)"
- "Dave Bendit (@DBendit)"
requirements:
- "L(Docker SDK for Python,https://docker-py.readthedocs.io/en/stable/) >= 1.10.0 (use L(docker-py,https://pypi.org/project/docker-py/) for Python 2.6)"
- "The docker server >= 1.10.0"
'''
EXAMPLES = '''
- name: Create a network
community.general.docker_network:
name: network_one
- name: Remove all but selected list of containers
community.general.docker_network:
name: network_one
connected:
- container_a
- container_b
- container_c
- name: Remove a single container
community.general.docker_network:
name: network_one
connected: "{{ fulllist|difference(['container_a']) }}"
- name: Add a container to a network, leaving existing containers connected
community.general.docker_network:
name: network_one
connected:
- container_a
appends: yes
- name: Create a network with driver options
community.general.docker_network:
name: network_two
driver_options:
com.docker.network.bridge.name: net2
- name: Create a network with custom IPAM config
community.general.docker_network:
name: network_three
ipam_config:
- subnet: 172.3.27.0/24
gateway: 172.3.27.2
iprange: 172.3.27.0/26
aux_addresses:
host1: 172.3.27.3
host2: 172.3.27.4
- name: Create a network with labels
community.general.docker_network:
name: network_four
labels:
key1: value1
key2: value2
- name: Create a network with IPv6 IPAM config
community.general.docker_network:
name: network_ipv6_one
enable_ipv6: yes
ipam_config:
- subnet: fdd1:ac8c:0557:7ce1::/64
- name: Create a network with IPv6 and custom IPv4 IPAM config
community.general.docker_network:
name: network_ipv6_two
enable_ipv6: yes
ipam_config:
- subnet: 172.4.27.0/24
- subnet: fdd1:ac8c:0557:7ce2::/64
- name: Delete a network, disconnecting all containers
community.general.docker_network:
name: network_one
state: absent
force: yes
'''
RETURN = '''
network:
description:
- Network inspection results for the affected network.
- Note that facts are part of the registered vars since Ansible 2.8. For compatibility reasons, the facts
are also accessible directly as C(docker_network). Note that the returned fact will be removed in community.general 2.0.0.
returned: success
type: dict
sample: {}
'''
import re
import traceback
from distutils.version import LooseVersion
from ansible_collections.community.general.plugins.module_utils.docker.common import (
AnsibleDockerClient,
DockerBaseClass,
docker_version,
DifferenceTracker,
clean_dict_booleans_for_docker_api,
RequestException,
)
try:
from docker import utils
from docker.errors import DockerException
if LooseVersion(docker_version) >= LooseVersion('2.0.0'):
from docker.types import IPAMPool, IPAMConfig
except Exception:
# missing Docker SDK for Python handled in ansible.module_utils.docker.common
pass
class TaskParameters(DockerBaseClass):
def __init__(self, client):
super(TaskParameters, self).__init__()
self.client = client
self.name = None
self.connected = None
self.driver = None
self.driver_options = None
self.ipam_driver = None
self.ipam_driver_options = None
self.ipam_options = None
self.ipam_config = None
self.appends = None
self.force = None
self.internal = None
self.labels = None
self.debug = None
self.enable_ipv6 = None
self.scope = None
self.attachable = None
for key, value in client.module.params.items():
setattr(self, key, value)
def container_names_in_network(network):
return [c['Name'] for c in network['Containers'].values()] if network['Containers'] else []
CIDR_IPV4 = re.compile(r'^([0-9]{1,3}\.){3}[0-9]{1,3}/([0-9]|[1-2][0-9]|3[0-2])$')
CIDR_IPV6 = re.compile(r'^[0-9a-fA-F:]+/([0-9]|[1-9][0-9]|1[0-2][0-9])$')
def validate_cidr(cidr):
"""Validate CIDR. Return IP version of a CIDR string on success.
:param cidr: Valid CIDR
:type cidr: str
:return: ``ipv4`` or ``ipv6``
:rtype: str
:raises ValueError: If ``cidr`` is not a valid CIDR
"""
if CIDR_IPV4.match(cidr):
return 'ipv4'
elif CIDR_IPV6.match(cidr):
return 'ipv6'
raise ValueError('"{0}" is not a valid CIDR'.format(cidr))
def normalize_ipam_config_key(key):
"""Normalizes IPAM config keys returned by Docker API to match Ansible keys.
:param key: Docker API key
:type key: str
:return Ansible module key
:rtype str
"""
special_cases = {
'AuxiliaryAddresses': 'aux_addresses'
}
return special_cases.get(key, key.lower())
def dicts_are_essentially_equal(a, b):
"""Make sure that a is a subset of b, where None entries of a are ignored."""
for k, v in a.items():
if v is None:
continue
if b.get(k) != v:
return False
return True
class DockerNetworkManager(object):
def __init__(self, client):
self.client = client
self.parameters = TaskParameters(client)
self.check_mode = self.client.check_mode
self.results = {
u'changed': False,
u'actions': []
}
self.diff = self.client.module._diff
self.diff_tracker = DifferenceTracker()
self.diff_result = dict()
self.existing_network = self.get_existing_network()
if not self.parameters.connected and self.existing_network:
self.parameters.connected = container_names_in_network(self.existing_network)
if (self.parameters.ipam_options['subnet'] or self.parameters.ipam_options['iprange'] or
self.parameters.ipam_options['gateway'] or self.parameters.ipam_options['aux_addresses']):
self.parameters.ipam_config = [self.parameters.ipam_options]
if self.parameters.ipam_config:
try:
for ipam_config in self.parameters.ipam_config:
validate_cidr(ipam_config['subnet'])
except ValueError as e:
self.client.fail(str(e))
if self.parameters.driver_options:
self.parameters.driver_options = clean_dict_booleans_for_docker_api(self.parameters.driver_options)
state = self.parameters.state
if state == 'present':
self.present()
elif state == 'absent':
self.absent()
if self.diff or self.check_mode or self.parameters.debug:
if self.diff:
self.diff_result['before'], self.diff_result['after'] = self.diff_tracker.get_before_after()
self.results['diff'] = self.diff_result
def get_existing_network(self):
return self.client.get_network(name=self.parameters.name)
def has_different_config(self, net):
'''
Evaluates an existing network and returns a tuple containing a boolean
indicating if the configuration is different and a list of differences.
:param net: the inspection output for an existing network
:return: (bool, list)
'''
differences = DifferenceTracker()
if self.parameters.driver and self.parameters.driver != net['Driver']:
differences.add('driver',
parameter=self.parameters.driver,
active=net['Driver'])
if self.parameters.driver_options:
if not net.get('Options'):
differences.add('driver_options',
parameter=self.parameters.driver_options,
active=net.get('Options'))
else:
for key, value in self.parameters.driver_options.items():
if not (key in net['Options']) or value != net['Options'][key]:
differences.add('driver_options.%s' % key,
parameter=value,
active=net['Options'].get(key))
if self.parameters.ipam_driver:
if not net.get('IPAM') or net['IPAM']['Driver'] != self.parameters.ipam_driver:
differences.add('ipam_driver',
parameter=self.parameters.ipam_driver,
active=net.get('IPAM'))
if self.parameters.ipam_driver_options is not None:
ipam_driver_options = net['IPAM'].get('Options') or {}
if ipam_driver_options != self.parameters.ipam_driver_options:
differences.add('ipam_driver_options',
parameter=self.parameters.ipam_driver_options,
active=ipam_driver_options)
if self.parameters.ipam_config is not None and self.parameters.ipam_config:
if not net.get('IPAM') or not net['IPAM']['Config']:
differences.add('ipam_config',
parameter=self.parameters.ipam_config,
active=net.get('IPAM', {}).get('Config'))
else:
# Put network's IPAM config into the same format as module's IPAM config
net_ipam_configs = []
for net_ipam_config in net['IPAM']['Config']:
config = dict()
for k, v in net_ipam_config.items():
config[normalize_ipam_config_key(k)] = v
net_ipam_configs.append(config)
# Compare lists of dicts as sets of dicts
for idx, ipam_config in enumerate(self.parameters.ipam_config):
net_config = dict()
for net_ipam_config in net_ipam_configs:
if dicts_are_essentially_equal(ipam_config, net_ipam_config):
net_config = net_ipam_config
break
for key, value in ipam_config.items():
if value is None:
# due to recursive argument_spec, all keys are always present
# (but have default value None if not specified)
continue
if value != net_config.get(key):
differences.add('ipam_config[%s].%s' % (idx, key),
parameter=value,
active=net_config.get(key))
if self.parameters.enable_ipv6 is not None and self.parameters.enable_ipv6 != net.get('EnableIPv6', False):
differences.add('enable_ipv6',
parameter=self.parameters.enable_ipv6,
active=net.get('EnableIPv6', False))
if self.parameters.internal is not None and self.parameters.internal != net.get('Internal', False):
differences.add('internal',
parameter=self.parameters.internal,
active=net.get('Internal'))
if self.parameters.scope is not None and self.parameters.scope != net.get('Scope'):
differences.add('scope',
parameter=self.parameters.scope,
active=net.get('Scope'))
if self.parameters.attachable is not None and self.parameters.attachable != net.get('Attachable', False):
differences.add('attachable',
parameter=self.parameters.attachable,
active=net.get('Attachable'))
if self.parameters.labels:
if not net.get('Labels'):
differences.add('labels',
parameter=self.parameters.labels,
active=net.get('Labels'))
else:
for key, value in self.parameters.labels.items():
if not (key in net['Labels']) or value != net['Labels'][key]:
differences.add('labels.%s' % key,
parameter=value,
active=net['Labels'].get(key))
return not differences.empty, differences
def create_network(self):
if not self.existing_network:
params = dict(
driver=self.parameters.driver,
options=self.parameters.driver_options,
)
ipam_pools = []
if self.parameters.ipam_config:
for ipam_pool in self.parameters.ipam_config:
if LooseVersion(docker_version) >= LooseVersion('2.0.0'):
ipam_pools.append(IPAMPool(**ipam_pool))
else:
ipam_pools.append(utils.create_ipam_pool(**ipam_pool))
if self.parameters.ipam_driver or self.parameters.ipam_driver_options or ipam_pools:
# Only add ipam parameter if a driver was specified or if IPAM parameters
# were specified. Leaving this parameter away can significantly speed up
# creation; on my machine creation with this option needs ~15 seconds,
# and without just a few seconds.
if LooseVersion(docker_version) >= LooseVersion('2.0.0'):
params['ipam'] = IPAMConfig(driver=self.parameters.ipam_driver,
pool_configs=ipam_pools,
options=self.parameters.ipam_driver_options)
else:
params['ipam'] = utils.create_ipam_config(driver=self.parameters.ipam_driver,
pool_configs=ipam_pools)
if self.parameters.enable_ipv6 is not None:
params['enable_ipv6'] = self.parameters.enable_ipv6
if self.parameters.internal is not None:
params['internal'] = self.parameters.internal
if self.parameters.scope is not None:
params['scope'] = self.parameters.scope
if self.parameters.attachable is not None:
params['attachable'] = self.parameters.attachable
if self.parameters.labels:
params['labels'] = self.parameters.labels
if not self.check_mode:
resp = self.client.create_network(self.parameters.name, **params)
self.client.report_warnings(resp, ['Warning'])
self.existing_network = self.client.get_network(network_id=resp['Id'])
self.results['actions'].append("Created network %s with driver %s" % (self.parameters.name, self.parameters.driver))
self.results['changed'] = True
def remove_network(self):
if self.existing_network:
self.disconnect_all_containers()
if not self.check_mode:
self.client.remove_network(self.parameters.name)
self.results['actions'].append("Removed network %s" % (self.parameters.name,))
self.results['changed'] = True
def is_container_connected(self, container_name):
if not self.existing_network:
return False
return container_name in container_names_in_network(self.existing_network)
def connect_containers(self):
for name in self.parameters.connected:
if not self.is_container_connected(name):
if not self.check_mode:
self.client.connect_container_to_network(name, self.parameters.name)
self.results['actions'].append("Connected container %s" % (name,))
self.results['changed'] = True
self.diff_tracker.add('connected.{0}'.format(name),
parameter=True,
active=False)
def disconnect_missing(self):
if not self.existing_network:
return
containers = self.existing_network['Containers']
if not containers:
return
for c in containers.values():
name = c['Name']
if name not in self.parameters.connected:
self.disconnect_container(name)
def disconnect_all_containers(self):
containers = self.client.get_network(name=self.parameters.name)['Containers']
if not containers:
return
for cont in containers.values():
self.disconnect_container(cont['Name'])
def disconnect_container(self, container_name):
if not self.check_mode:
self.client.disconnect_container_from_network(container_name, self.parameters.name)
self.results['actions'].append("Disconnected container %s" % (container_name,))
self.results['changed'] = True
self.diff_tracker.add('connected.{0}'.format(container_name),
parameter=False,
active=True)
def present(self):
different = False
differences = DifferenceTracker()
if self.existing_network:
different, differences = self.has_different_config(self.existing_network)
self.diff_tracker.add('exists', parameter=True, active=self.existing_network is not None)
if self.parameters.force or different:
self.remove_network()
self.existing_network = None
self.create_network()
self.connect_containers()
if not self.parameters.appends:
self.disconnect_missing()
if self.diff or self.check_mode or self.parameters.debug:
self.diff_result['differences'] = differences.get_legacy_docker_diffs()
self.diff_tracker.merge(differences)
if not self.check_mode and not self.parameters.debug:
self.results.pop('actions')
network_facts = self.get_existing_network()
self.results['ansible_facts'] = {u'docker_network': network_facts}
self.results['network'] = network_facts
def absent(self):
self.diff_tracker.add('exists', parameter=False, active=self.existing_network is not None)
self.remove_network()
def main():
argument_spec = dict(
name=dict(type='str', required=True, aliases=['network_name']),
connected=dict(type='list', default=[], elements='str', aliases=['containers']),
state=dict(type='str', default='present', choices=['present', 'absent']),
driver=dict(type='str', default='bridge'),
driver_options=dict(type='dict', default={}),
force=dict(type='bool', default=False),
appends=dict(type='bool', default=False, aliases=['incremental']),
ipam_driver=dict(type='str'),
ipam_driver_options=dict(type='dict'),
ipam_options=dict(type='dict', default={}, options=dict(
subnet=dict(type='str'),
iprange=dict(type='str'),
gateway=dict(type='str'),
aux_addresses=dict(type='dict'),
), removed_in_version='2.0.0', removed_from_collection='community.general'), # was Ansible 2.12
ipam_config=dict(type='list', elements='dict', options=dict(
subnet=dict(type='str'),
iprange=dict(type='str'),
gateway=dict(type='str'),
aux_addresses=dict(type='dict'),
)),
enable_ipv6=dict(type='bool'),
internal=dict(type='bool'),
labels=dict(type='dict', default={}),
debug=dict(type='bool', default=False),
scope=dict(type='str', choices=['local', 'global', 'swarm']),
attachable=dict(type='bool'),
)
mutually_exclusive = [
('ipam_config', 'ipam_options')
]
option_minimal_versions = dict(
scope=dict(docker_py_version='2.6.0', docker_api_version='1.30'),
attachable=dict(docker_py_version='2.0.0', docker_api_version='1.26'),
labels=dict(docker_api_version='1.23'),
ipam_driver_options=dict(docker_py_version='2.0.0'),
)
client = AnsibleDockerClient(
argument_spec=argument_spec,
mutually_exclusive=mutually_exclusive,
supports_check_mode=True,
min_docker_version='1.10.0',
min_docker_api_version='1.22',
# "The docker server >= 1.10.0"
option_minimal_versions=option_minimal_versions,
)
try:
cm = DockerNetworkManager(client)
client.module.exit_json(**cm.results)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
except RequestException as e:
client.fail('An unexpected requests error occurred when docker-py tried to talk to the docker daemon: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':
main()

View file

@ -1,141 +0,0 @@
#!/usr/bin/python
#
# Copyright 2016 Red Hat | Ansible
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
---
module: docker_network_info
short_description: Retrieves facts about docker network
description:
- Retrieves facts about a docker network.
- Essentially returns the output of C(docker network inspect <name>), similar to what M(community.general.docker_network)
returns for a non-absent network.
options:
name:
description:
- The name of the network to inspect.
- When identifying an existing network name may be a name or a long or short network ID.
type: str
required: yes
extends_documentation_fragment:
- community.general.docker
- community.general.docker.docker_py_1_documentation
author:
- "Dave Bendit (@DBendit)"
requirements:
- "L(Docker SDK for Python,https://docker-py.readthedocs.io/en/stable/) >= 1.8.0 (use L(docker-py,https://pypi.org/project/docker-py/) for Python 2.6)"
- "Docker API >= 1.21"
'''
EXAMPLES = '''
- name: Get infos on network
community.general.docker_network_info:
name: mydata
register: result
- name: Does network exist?
ansible.builtin.debug:
msg: "The network {{ 'exists' if result.exists else 'does not exist' }}"
- name: Print information about network
ansible.builtin.debug:
var: result.network
when: result.exists
'''
RETURN = '''
exists:
description:
- Returns whether the network exists.
type: bool
returned: always
sample: true
network:
description:
- Facts representing the current state of the network. Matches the docker inspection output.
- Will be C(none) if network does not exist.
returned: always
type: dict
sample: '{
"Attachable": false,
"ConfigFrom": {
"Network": ""
},
"ConfigOnly": false,
"Containers": {},
"Created": "2018-12-07T01:47:51.250835114-06:00",
"Driver": "bridge",
"EnableIPv6": false,
"IPAM": {
"Config": [
{
"Gateway": "192.168.96.1",
"Subnet": "192.168.96.0/20"
}
],
"Driver": "default",
"Options": null
},
"Id": "0856968545f22026c41c2c7c3d448319d3b4a6a03a40b148b3ac4031696d1c0a",
"Ingress": false,
"Internal": false,
"Labels": {},
"Name": "ansible-test-f2700bba",
"Options": {},
"Scope": "local"
}'
'''
import traceback
try:
from docker.errors import DockerException
except ImportError:
# missing Docker SDK for Python handled in ansible.module_utils.docker.common
pass
from ansible_collections.community.general.plugins.module_utils.docker.common import (
AnsibleDockerClient,
RequestException,
)
def main():
argument_spec = dict(
name=dict(type='str', required=True),
)
client = AnsibleDockerClient(
argument_spec=argument_spec,
supports_check_mode=True,
min_docker_api_version='1.21',
)
try:
network = client.get_network(client.module.params['name'])
client.module.exit_json(
changed=False,
exists=(True if network else False),
network=network,
)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
except RequestException as e:
client.fail('An unexpected requests error occurred when docker-py tried to talk to the docker daemon: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':
main()

View file

@ -1,294 +0,0 @@
#!/usr/bin/python
#
# (c) 2019 Piotr Wojciechowski <piotr@it-playground.pl>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
---
module: docker_node
short_description: Manage Docker Swarm node
description:
- Manages the Docker nodes via Swarm Manager.
- This module allows to change the node's role, its availability, and to modify, add or remove node labels.
options:
hostname:
description:
- The hostname or ID of node as registered in Swarm.
- If more than one node is registered using the same hostname the ID must be used,
otherwise module will fail.
type: str
required: yes
labels:
description:
- User-defined key/value metadata that will be assigned as node attribute.
- Label operations in this module apply to the docker swarm node specified by I(hostname).
Use M(community.general.docker_swarm) module to add/modify/remove swarm cluster labels.
- The actual state of labels assigned to the node when module completes its work depends on
I(labels_state) and I(labels_to_remove) parameters values. See description below.
type: dict
labels_state:
description:
- It defines the operation on the labels assigned to node and labels specified in I(labels) option.
- Set to C(merge) to combine labels provided in I(labels) with those already assigned to the node.
If no labels are assigned then it will add listed labels. For labels that are already assigned
to the node, it will update their values. The labels not specified in I(labels) will remain unchanged.
If I(labels) is empty then no changes will be made.
- Set to C(replace) to replace all assigned labels with provided ones. If I(labels) is empty then
all labels assigned to the node will be removed.
type: str
default: 'merge'
choices:
- merge
- replace
labels_to_remove:
description:
- List of labels that will be removed from the node configuration. The list has to contain only label
names, not their values.
- If the label provided on the list is not assigned to the node, the entry is ignored.
- If the label is both on the I(labels_to_remove) and I(labels), then value provided in I(labels) remains
assigned to the node.
- If I(labels_state) is C(replace) and I(labels) is not provided or empty then all labels assigned to
node are removed and I(labels_to_remove) is ignored.
type: list
elements: str
availability:
description: Node availability to assign. If not provided then node availability remains unchanged.
choices:
- active
- pause
- drain
type: str
role:
description: Node role to assign. If not provided then node role remains unchanged.
choices:
- manager
- worker
type: str
extends_documentation_fragment:
- community.general.docker
- community.general.docker.docker_py_1_documentation
requirements:
- "L(Docker SDK for Python,https://docker-py.readthedocs.io/en/stable/) >= 2.4.0"
- Docker API >= 1.25
author:
- Piotr Wojciechowski (@WojciechowskiPiotr)
- Thierry Bouvet (@tbouvet)
'''
EXAMPLES = '''
- name: Set node role
community.general.docker_node:
hostname: mynode
role: manager
- name: Set node availability
community.general.docker_node:
hostname: mynode
availability: drain
- name: Replace node labels with new labels
community.general.docker_node:
hostname: mynode
labels:
key: value
labels_state: replace
- name: Merge node labels and new labels
community.general.docker_node:
hostname: mynode
labels:
key: value
- name: Remove all labels assigned to node
community.general.docker_node:
hostname: mynode
labels_state: replace
- name: Remove selected labels from the node
community.general.docker_node:
hostname: mynode
labels_to_remove:
- key1
- key2
'''
RETURN = '''
node:
description: Information about node after 'update' operation
returned: success
type: dict
'''
import traceback
try:
from docker.errors import DockerException, APIError
except ImportError:
# missing Docker SDK for Python handled in ansible.module_utils.docker.common
pass
from ansible_collections.community.general.plugins.module_utils.docker.common import (
DockerBaseClass,
RequestException,
)
from ansible.module_utils._text import to_native
from ansible_collections.community.general.plugins.module_utils.docker.swarm import AnsibleDockerSwarmClient
class TaskParameters(DockerBaseClass):
def __init__(self, client):
super(TaskParameters, self).__init__()
# Spec
self.name = None
self.labels = None
self.labels_state = None
self.labels_to_remove = None
# Node
self.availability = None
self.role = None
for key, value in client.module.params.items():
setattr(self, key, value)
class SwarmNodeManager(DockerBaseClass):
def __init__(self, client, results):
super(SwarmNodeManager, self).__init__()
self.client = client
self.results = results
self.check_mode = self.client.check_mode
self.client.fail_task_if_not_swarm_manager()
self.parameters = TaskParameters(client)
self.node_update()
def node_update(self):
if not (self.client.check_if_swarm_node(node_id=self.parameters.hostname)):
self.client.fail("This node is not part of a swarm.")
return
if self.client.check_if_swarm_node_is_down():
self.client.fail("Can not update the node. The node is down.")
try:
node_info = self.client.inspect_node(node_id=self.parameters.hostname)
except APIError as exc:
self.client.fail("Failed to get node information for %s" % to_native(exc))
changed = False
node_spec = dict(
Availability=self.parameters.availability,
Role=self.parameters.role,
Labels=self.parameters.labels,
)
if self.parameters.role is None:
node_spec['Role'] = node_info['Spec']['Role']
else:
if not node_info['Spec']['Role'] == self.parameters.role:
node_spec['Role'] = self.parameters.role
changed = True
if self.parameters.availability is None:
node_spec['Availability'] = node_info['Spec']['Availability']
else:
if not node_info['Spec']['Availability'] == self.parameters.availability:
node_info['Spec']['Availability'] = self.parameters.availability
changed = True
if self.parameters.labels_state == 'replace':
if self.parameters.labels is None:
node_spec['Labels'] = {}
if node_info['Spec']['Labels']:
changed = True
else:
if (node_info['Spec']['Labels'] or {}) != self.parameters.labels:
node_spec['Labels'] = self.parameters.labels
changed = True
elif self.parameters.labels_state == 'merge':
node_spec['Labels'] = dict(node_info['Spec']['Labels'] or {})
if self.parameters.labels is not None:
for key, value in self.parameters.labels.items():
if node_spec['Labels'].get(key) != value:
node_spec['Labels'][key] = value
changed = True
if self.parameters.labels_to_remove is not None:
for key in self.parameters.labels_to_remove:
if self.parameters.labels is not None:
if not self.parameters.labels.get(key):
if node_spec['Labels'].get(key):
node_spec['Labels'].pop(key)
changed = True
else:
self.client.module.warn(
"Label '%s' listed both in 'labels' and 'labels_to_remove'. "
"Keeping the assigned label value."
% to_native(key))
else:
if node_spec['Labels'].get(key):
node_spec['Labels'].pop(key)
changed = True
if changed is True:
if not self.check_mode:
try:
self.client.update_node(node_id=node_info['ID'], version=node_info['Version']['Index'],
node_spec=node_spec)
except APIError as exc:
self.client.fail("Failed to update node : %s" % to_native(exc))
self.results['node'] = self.client.get_node_inspect(node_id=node_info['ID'])
self.results['changed'] = changed
else:
self.results['node'] = node_info
self.results['changed'] = changed
def main():
argument_spec = dict(
hostname=dict(type='str', required=True),
labels=dict(type='dict'),
labels_state=dict(type='str', default='merge', choices=['merge', 'replace']),
labels_to_remove=dict(type='list', elements='str'),
availability=dict(type='str', choices=['active', 'pause', 'drain']),
role=dict(type='str', choices=['worker', 'manager']),
)
client = AnsibleDockerSwarmClient(
argument_spec=argument_spec,
supports_check_mode=True,
min_docker_version='2.4.0',
min_docker_api_version='1.25',
)
try:
results = dict(
changed=False,
)
SwarmNodeManager(client, results)
client.module.exit_json(**results)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
except RequestException as e:
client.fail('An unexpected requests error occurred when docker-py tried to talk to the docker daemon: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':
main()

View file

@ -1,156 +0,0 @@
#!/usr/bin/python
#
# (c) 2019 Piotr Wojciechowski <piotr@it-playground.pl>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
---
module: docker_node_info
short_description: Retrieves facts about docker swarm node from Swarm Manager
description:
- Retrieves facts about a docker node.
- Essentially returns the output of C(docker node inspect <name>).
- Must be executed on a host running as Swarm Manager, otherwise the module will fail.
options:
name:
description:
- The name of the node to inspect.
- The list of nodes names to inspect.
- If empty then return information of all nodes in Swarm cluster.
- When identifying the node use either the hostname of the node (as registered in Swarm) or node ID.
- If I(self) is C(true) then this parameter is ignored.
type: list
elements: str
self:
description:
- If C(true), queries the node (i.e. the docker daemon) the module communicates with.
- If C(true) then I(name) is ignored.
- If C(false) then query depends on I(name) presence and value.
type: bool
default: no
extends_documentation_fragment:
- community.general.docker
- community.general.docker.docker_py_1_documentation
author:
- Piotr Wojciechowski (@WojciechowskiPiotr)
requirements:
- "L(Docker SDK for Python,https://docker-py.readthedocs.io/en/stable/) >= 2.4.0"
- "Docker API >= 1.24"
'''
EXAMPLES = '''
- name: Get info on all nodes
community.general.docker_node_info:
register: result
- name: Get info on node
community.general.docker_node_info:
name: mynode
register: result
- name: Get info on list of nodes
community.general.docker_node_info:
name:
- mynode1
- mynode2
register: result
- name: Get info on host if it is Swarm Manager
community.general.docker_node_info:
self: true
register: result
'''
RETURN = '''
nodes:
description:
- Facts representing the current state of the nodes. Matches the C(docker node inspect) output.
- Can contain multiple entries if more than one node provided in I(name), or I(name) is not provided.
- If I(name) contains a list of nodes, the output will provide information on all nodes registered
at the swarm, including nodes that left the swarm but haven't been removed from the cluster on swarm
managers and nodes that are unreachable.
returned: always
type: list
elements: dict
'''
import traceback
from ansible_collections.community.general.plugins.module_utils.docker.common import (
RequestException,
)
from ansible_collections.community.general.plugins.module_utils.docker.swarm import AnsibleDockerSwarmClient
try:
from docker.errors import DockerException
except ImportError:
# missing Docker SDK for Python handled in ansible.module_utils.docker.common
pass
def get_node_facts(client):
results = []
if client.module.params['self'] is True:
self_node_id = client.get_swarm_node_id()
node_info = client.get_node_inspect(node_id=self_node_id)
results.append(node_info)
return results
if client.module.params['name'] is None:
node_info = client.get_all_nodes_inspect()
return node_info
nodes = client.module.params['name']
if not isinstance(nodes, list):
nodes = [nodes]
for next_node_name in nodes:
next_node_info = client.get_node_inspect(node_id=next_node_name, skip_missing=True)
if next_node_info:
results.append(next_node_info)
return results
def main():
argument_spec = dict(
name=dict(type='list', elements='str'),
self=dict(type='bool', default=False),
)
client = AnsibleDockerSwarmClient(
argument_spec=argument_spec,
supports_check_mode=True,
min_docker_version='2.4.0',
min_docker_api_version='1.24',
)
client.fail_task_if_not_swarm_manager()
try:
nodes = get_node_facts(client)
client.module.exit_json(
changed=False,
nodes=nodes,
)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
except RequestException as e:
client.fail('An unexpected requests error occurred when docker-py tried to talk to the docker daemon: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':
main()

View file

@ -1,265 +0,0 @@
#!/usr/bin/python
#
# Copyright 2016 Red Hat | Ansible
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
---
module: docker_prune
short_description: Allows to prune various docker objects
description:
- Allows to run C(docker container prune), C(docker image prune), C(docker network prune)
and C(docker volume prune) via the Docker API.
options:
containers:
description:
- Whether to prune containers.
type: bool
default: no
containers_filters:
description:
- A dictionary of filter values used for selecting containers to delete.
- "For example, C(until: 24h)."
- See L(the docker documentation,https://docs.docker.com/engine/reference/commandline/container_prune/#filtering)
for more information on possible filters.
type: dict
images:
description:
- Whether to prune images.
type: bool
default: no
images_filters:
description:
- A dictionary of filter values used for selecting images to delete.
- "For example, C(dangling: true)."
- See L(the docker documentation,https://docs.docker.com/engine/reference/commandline/image_prune/#filtering)
for more information on possible filters.
type: dict
networks:
description:
- Whether to prune networks.
type: bool
default: no
networks_filters:
description:
- A dictionary of filter values used for selecting networks to delete.
- See L(the docker documentation,https://docs.docker.com/engine/reference/commandline/network_prune/#filtering)
for more information on possible filters.
type: dict
volumes:
description:
- Whether to prune volumes.
type: bool
default: no
volumes_filters:
description:
- A dictionary of filter values used for selecting volumes to delete.
- See L(the docker documentation,https://docs.docker.com/engine/reference/commandline/volume_prune/#filtering)
for more information on possible filters.
type: dict
builder_cache:
description:
- Whether to prune the builder cache.
- Requires version 3.3.0 of the Docker SDK for Python or newer.
type: bool
default: no
extends_documentation_fragment:
- community.general.docker
- community.general.docker.docker_py_2_documentation
author:
- "Felix Fontein (@felixfontein)"
requirements:
- "L(Docker SDK for Python,https://docker-py.readthedocs.io/en/stable/) >= 2.1.0"
- "Docker API >= 1.25"
'''
EXAMPLES = '''
- name: Prune containers older than 24h
community.general.docker_prune:
containers: yes
containers_filters:
# only consider containers created more than 24 hours ago
until: 24h
- name: Prune everything
community.general.docker_prune:
containers: yes
images: yes
networks: yes
volumes: yes
builder_cache: yes
- name: Prune everything (including non-dangling images)
community.general.docker_prune:
containers: yes
images: yes
images_filters:
dangling: false
networks: yes
volumes: yes
builder_cache: yes
'''
RETURN = '''
# containers
containers:
description:
- List of IDs of deleted containers.
returned: I(containers) is C(true)
type: list
elements: str
sample: '[]'
containers_space_reclaimed:
description:
- Amount of reclaimed disk space from container pruning in bytes.
returned: I(containers) is C(true)
type: int
sample: '0'
# images
images:
description:
- List of IDs of deleted images.
returned: I(images) is C(true)
type: list
elements: str
sample: '[]'
images_space_reclaimed:
description:
- Amount of reclaimed disk space from image pruning in bytes.
returned: I(images) is C(true)
type: int
sample: '0'
# networks
networks:
description:
- List of IDs of deleted networks.
returned: I(networks) is C(true)
type: list
elements: str
sample: '[]'
# volumes
volumes:
description:
- List of IDs of deleted volumes.
returned: I(volumes) is C(true)
type: list
elements: str
sample: '[]'
volumes_space_reclaimed:
description:
- Amount of reclaimed disk space from volumes pruning in bytes.
returned: I(volumes) is C(true)
type: int
sample: '0'
# builder_cache
builder_cache_space_reclaimed:
description:
- Amount of reclaimed disk space from builder cache pruning in bytes.
returned: I(builder_cache) is C(true)
type: int
sample: '0'
'''
import traceback
try:
from docker.errors import DockerException
except ImportError:
# missing Docker SDK for Python handled in ansible.module_utils.docker.common
pass
from distutils.version import LooseVersion
from ansible_collections.community.general.plugins.module_utils.docker.common import (
AnsibleDockerClient,
RequestException,
)
try:
from ansible_collections.community.general.plugins.module_utils.docker.common import docker_version, clean_dict_booleans_for_docker_api
except Exception as dummy:
# missing Docker SDK for Python handled in ansible.module_utils.docker.common
pass
def main():
argument_spec = dict(
containers=dict(type='bool', default=False),
containers_filters=dict(type='dict'),
images=dict(type='bool', default=False),
images_filters=dict(type='dict'),
networks=dict(type='bool', default=False),
networks_filters=dict(type='dict'),
volumes=dict(type='bool', default=False),
volumes_filters=dict(type='dict'),
builder_cache=dict(type='bool', default=False),
)
client = AnsibleDockerClient(
argument_spec=argument_spec,
# supports_check_mode=True,
min_docker_api_version='1.25',
min_docker_version='2.1.0',
)
# Version checks
cache_min_version = '3.3.0'
if client.module.params['builder_cache'] and client.docker_py_version < LooseVersion(cache_min_version):
msg = "Error: Docker SDK for Python's version is %s. Minimum version required for builds option is %s. Use `pip install --upgrade docker` to upgrade."
client.fail(msg % (docker_version, cache_min_version))
try:
result = dict()
if client.module.params['containers']:
filters = clean_dict_booleans_for_docker_api(client.module.params.get('containers_filters'))
res = client.prune_containers(filters=filters)
result['containers'] = res.get('ContainersDeleted') or []
result['containers_space_reclaimed'] = res['SpaceReclaimed']
if client.module.params['images']:
filters = clean_dict_booleans_for_docker_api(client.module.params.get('images_filters'))
res = client.prune_images(filters=filters)
result['images'] = res.get('ImagesDeleted') or []
result['images_space_reclaimed'] = res['SpaceReclaimed']
if client.module.params['networks']:
filters = clean_dict_booleans_for_docker_api(client.module.params.get('networks_filters'))
res = client.prune_networks(filters=filters)
result['networks'] = res.get('NetworksDeleted') or []
if client.module.params['volumes']:
filters = clean_dict_booleans_for_docker_api(client.module.params.get('volumes_filters'))
res = client.prune_volumes(filters=filters)
result['volumes'] = res.get('VolumesDeleted') or []
result['volumes_space_reclaimed'] = res['SpaceReclaimed']
if client.module.params['builder_cache']:
res = client.prune_builds()
result['builder_cache_space_reclaimed'] = res['SpaceReclaimed']
client.module.exit_json(**result)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
except RequestException as e:
client.fail('An unexpected requests error occurred when docker-py tried to talk to the docker daemon: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':
main()

View file

@ -1,302 +0,0 @@
#!/usr/bin/python
#
# Copyright 2016 Red Hat | Ansible
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
---
module: docker_secret
short_description: Manage docker secrets.
description:
- Create and remove Docker secrets in a Swarm environment. Similar to C(docker secret create) and C(docker secret rm).
- Adds to the metadata of new secrets 'ansible_key', an encrypted hash representation of the data, which is then used
in future runs to test if a secret has changed. If 'ansible_key is not present, then a secret will not be updated
unless the I(force) option is set.
- Updates to secrets are performed by removing the secret and creating it again.
options:
data:
description:
- The value of the secret. Required when state is C(present).
type: str
data_is_b64:
description:
- If set to C(true), the data is assumed to be Base64 encoded and will be
decoded before being used.
- To use binary I(data), it is better to keep it Base64 encoded and let it
be decoded by this option.
type: bool
default: no
labels:
description:
- "A map of key:value meta data, where both key and value are expected to be strings."
- If new meta data is provided, or existing meta data is modified, the secret will be updated by removing it and creating it again.
type: dict
force:
description:
- Use with state C(present) to always remove and recreate an existing secret.
- If C(true), an existing secret will be replaced, even if it has not changed.
type: bool
default: no
name:
description:
- The name of the secret.
type: str
required: yes
state:
description:
- Set to C(present), if the secret should exist, and C(absent), if it should not.
type: str
default: present
choices:
- absent
- present
extends_documentation_fragment:
- community.general.docker
- community.general.docker.docker_py_2_documentation
requirements:
- "L(Docker SDK for Python,https://docker-py.readthedocs.io/en/stable/) >= 2.1.0"
- "Docker API >= 1.25"
author:
- Chris Houseknecht (@chouseknecht)
'''
EXAMPLES = '''
- name: Create secret foo (from a file on the control machine)
community.general.docker_secret:
name: foo
# If the file is JSON or binary, Ansible might modify it (because
# it is first decoded and later re-encoded). Base64-encoding the
# file directly after reading it prevents this to happen.
data: "{{ lookup('file', '/path/to/secret/file') | b64encode }}"
data_is_b64: true
state: present
- name: Change the secret data
community.general.docker_secret:
name: foo
data: Goodnight everyone!
labels:
bar: baz
one: '1'
state: present
- name: Add a new label
community.general.docker_secret:
name: foo
data: Goodnight everyone!
labels:
bar: baz
one: '1'
# Adding a new label will cause a remove/create of the secret
two: '2'
state: present
- name: No change
community.general.docker_secret:
name: foo
data: Goodnight everyone!
labels:
bar: baz
one: '1'
# Even though 'two' is missing, there is no change to the existing secret
state: present
- name: Update an existing label
community.general.docker_secret:
name: foo
data: Goodnight everyone!
labels:
bar: monkey # Changing a label will cause a remove/create of the secret
one: '1'
state: present
- name: Force the removal/creation of the secret
community.general.docker_secret:
name: foo
data: Goodnight everyone!
force: yes
state: present
- name: Remove secret foo
community.general.docker_secret:
name: foo
state: absent
'''
RETURN = '''
secret_id:
description:
- The ID assigned by Docker to the secret object.
returned: success and I(state) is C(present)
type: str
sample: 'hzehrmyjigmcp2gb6nlhmjqcv'
'''
import base64
import hashlib
import traceback
try:
from docker.errors import DockerException, APIError
except ImportError:
# missing Docker SDK for Python handled in ansible.module_utils.docker.common
pass
from ansible_collections.community.general.plugins.module_utils.docker.common import (
AnsibleDockerClient,
DockerBaseClass,
compare_generic,
RequestException,
)
from ansible.module_utils._text import to_native, to_bytes
class SecretManager(DockerBaseClass):
def __init__(self, client, results):
super(SecretManager, self).__init__()
self.client = client
self.results = results
self.check_mode = self.client.check_mode
parameters = self.client.module.params
self.name = parameters.get('name')
self.state = parameters.get('state')
self.data = parameters.get('data')
if self.data is not None:
if parameters.get('data_is_b64'):
self.data = base64.b64decode(self.data)
else:
self.data = to_bytes(self.data)
self.labels = parameters.get('labels')
self.force = parameters.get('force')
self.data_key = None
def __call__(self):
if self.state == 'present':
self.data_key = hashlib.sha224(self.data).hexdigest()
self.present()
elif self.state == 'absent':
self.absent()
def get_secret(self):
''' Find an existing secret. '''
try:
secrets = self.client.secrets(filters={'name': self.name})
except APIError as exc:
self.client.fail("Error accessing secret %s: %s" % (self.name, to_native(exc)))
for secret in secrets:
if secret['Spec']['Name'] == self.name:
return secret
return None
def create_secret(self):
''' Create a new secret '''
secret_id = None
# We can't see the data after creation, so adding a label we can use for idempotency check
labels = {
'ansible_key': self.data_key
}
if self.labels:
labels.update(self.labels)
try:
if not self.check_mode:
secret_id = self.client.create_secret(self.name, self.data, labels=labels)
except APIError as exc:
self.client.fail("Error creating secret: %s" % to_native(exc))
if isinstance(secret_id, dict):
secret_id = secret_id['ID']
return secret_id
def present(self):
''' Handles state == 'present', creating or updating the secret '''
secret = self.get_secret()
if secret:
self.results['secret_id'] = secret['ID']
data_changed = False
attrs = secret.get('Spec', {})
if attrs.get('Labels', {}).get('ansible_key'):
if attrs['Labels']['ansible_key'] != self.data_key:
data_changed = True
else:
if not self.force:
self.client.module.warn("'ansible_key' label not found. Secret will not be changed unless the force parameter is set to 'yes'")
labels_changed = not compare_generic(self.labels, attrs.get('Labels'), 'allow_more_present', 'dict')
if data_changed or labels_changed or self.force:
# if something changed or force, delete and re-create the secret
self.absent()
secret_id = self.create_secret()
self.results['changed'] = True
self.results['secret_id'] = secret_id
else:
self.results['changed'] = True
self.results['secret_id'] = self.create_secret()
def absent(self):
''' Handles state == 'absent', removing the secret '''
secret = self.get_secret()
if secret:
try:
if not self.check_mode:
self.client.remove_secret(secret['ID'])
except APIError as exc:
self.client.fail("Error removing secret %s: %s" % (self.name, to_native(exc)))
self.results['changed'] = True
def main():
argument_spec = dict(
name=dict(type='str', required=True),
state=dict(type='str', default='present', choices=['absent', 'present']),
data=dict(type='str', no_log=True),
data_is_b64=dict(type='bool', default=False),
labels=dict(type='dict'),
force=dict(type='bool', default=False)
)
required_if = [
('state', 'present', ['data'])
]
client = AnsibleDockerClient(
argument_spec=argument_spec,
supports_check_mode=True,
required_if=required_if,
min_docker_version='2.1.0',
min_docker_api_version='1.25',
)
try:
results = dict(
changed=False,
secret_id=''
)
SecretManager(client, results)()
client.module.exit_json(**results)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
except RequestException as e:
client.fail('An unexpected requests error occurred when docker-py tried to talk to the docker daemon: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':
main()

View file

@ -1 +0,0 @@
docker_compose.py

View file

@ -1,308 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2018 Dario Zanzico (git@dariozanzico.com)
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = '''
---
module: docker_stack
author: "Dario Zanzico (@dariko)"
short_description: docker stack module
description:
- Manage docker stacks using the 'docker stack' command
on the target node (see examples).
options:
name:
description:
- Stack name
type: str
required: yes
state:
description:
- Service state.
type: str
default: "present"
choices:
- present
- absent
compose:
description:
- List of compose definitions. Any element may be a string
referring to the path of the compose file on the target host
or the YAML contents of a compose file nested as dictionary.
type: list
elements: raw
default: []
prune:
description:
- If true will add the C(--prune) option to the C(docker stack deploy) command.
This will have docker remove the services not present in the
current stack definition.
type: bool
default: no
with_registry_auth:
description:
- If true will add the C(--with-registry-auth) option to the C(docker stack deploy) command.
This will have docker send registry authentication details to Swarm agents.
type: bool
default: no
resolve_image:
description:
- If set will add the C(--resolve-image) option to the C(docker stack deploy) command.
This will have docker query the registry to resolve image digest and
supported platforms. If not set, docker use "always" by default.
type: str
choices: ["always", "changed", "never"]
absent_retries:
description:
- If C(>0) and I(state) is C(absent) the module will retry up to
I(absent_retries) times to delete the stack until all the
resources have been effectively deleted.
If the last try still reports the stack as not completely
removed the module will fail.
type: int
default: 0
absent_retries_interval:
description:
- Interval in seconds between consecutive I(absent_retries).
type: int
default: 1
requirements:
- jsondiff
- pyyaml
notes:
- Return values I(out) and I(err) have been deprecated and will be removed in community.general 3.0.0. Use I(stdout) and I(stderr) instead.
'''
RETURN = '''
stack_spec_diff:
description: |
dictionary containing the differences between the 'Spec' field
of the stack services before and after applying the new stack
definition.
sample: >
"stack_spec_diff":
{'test_stack_test_service': {u'TaskTemplate': {u'ContainerSpec': {delete: [u'Env']}}}}
returned: on change
type: dict
'''
EXAMPLES = '''
- name: Deploy stack from a compose file
community.general.docker_stack:
state: present
name: mystack
compose:
- /opt/docker-compose.yml
- name: Deploy stack from base compose file and override the web service
community.general.docker_stack:
state: present
name: mystack
compose:
- /opt/docker-compose.yml
- version: '3'
services:
web:
image: nginx:latest
environment:
ENVVAR: envvar
- name: Remove stack
community.general.docker_stack:
name: mystack
state: absent
'''
import json
import tempfile
from ansible.module_utils.six import string_types
from time import sleep
try:
from jsondiff import diff as json_diff
HAS_JSONDIFF = True
except ImportError:
HAS_JSONDIFF = False
try:
from yaml import dump as yaml_dump
HAS_YAML = True
except ImportError:
HAS_YAML = False
from ansible.module_utils.basic import AnsibleModule, os
def docker_stack_services(module, stack_name):
docker_bin = module.get_bin_path('docker', required=True)
rc, out, err = module.run_command([docker_bin,
"stack",
"services",
stack_name,
"--format",
"{{.Name}}"])
if err == "Nothing found in stack: %s\n" % stack_name:
return []
return out.strip().split('\n')
def docker_service_inspect(module, service_name):
docker_bin = module.get_bin_path('docker', required=True)
rc, out, err = module.run_command([docker_bin,
"service",
"inspect",
service_name])
if rc != 0:
return None
else:
ret = json.loads(out)[0]['Spec']
return ret
def docker_stack_deploy(module, stack_name, compose_files):
docker_bin = module.get_bin_path('docker', required=True)
command = [docker_bin, "stack", "deploy"]
if module.params["prune"]:
command += ["--prune"]
if module.params["with_registry_auth"]:
command += ["--with-registry-auth"]
if module.params["resolve_image"]:
command += ["--resolve-image",
module.params["resolve_image"]]
for compose_file in compose_files:
command += ["--compose-file",
compose_file]
command += [stack_name]
return module.run_command(command)
def docker_stack_inspect(module, stack_name):
ret = {}
for service_name in docker_stack_services(module, stack_name):
ret[service_name] = docker_service_inspect(module, service_name)
return ret
def docker_stack_rm(module, stack_name, retries, interval):
docker_bin = module.get_bin_path('docker', required=True)
command = [docker_bin, "stack", "rm", stack_name]
rc, out, err = module.run_command(command)
while err != "Nothing found in stack: %s\n" % stack_name and retries > 0:
sleep(interval)
retries = retries - 1
rc, out, err = module.run_command(command)
return rc, out, err
def main():
module = AnsibleModule(
argument_spec={
'name': dict(type='str', required=True),
'compose': dict(type='list', elements='raw', default=[]),
'prune': dict(type='bool', default=False),
'with_registry_auth': dict(type='bool', default=False),
'resolve_image': dict(type='str', choices=['always', 'changed', 'never']),
'state': dict(type='str', default='present', choices=['present', 'absent']),
'absent_retries': dict(type='int', default=0),
'absent_retries_interval': dict(type='int', default=1)
},
supports_check_mode=False
)
if not HAS_JSONDIFF:
return module.fail_json(msg="jsondiff is not installed, try 'pip install jsondiff'")
if not HAS_YAML:
return module.fail_json(msg="yaml is not installed, try 'pip install pyyaml'")
state = module.params['state']
compose = module.params['compose']
name = module.params['name']
absent_retries = module.params['absent_retries']
absent_retries_interval = module.params['absent_retries_interval']
if state == 'present':
if not compose:
module.fail_json(msg=("compose parameter must be a list "
"containing at least one element"))
compose_files = []
for i, compose_def in enumerate(compose):
if isinstance(compose_def, dict):
compose_file_fd, compose_file = tempfile.mkstemp()
module.add_cleanup_file(compose_file)
with os.fdopen(compose_file_fd, 'w') as stack_file:
compose_files.append(compose_file)
stack_file.write(yaml_dump(compose_def))
elif isinstance(compose_def, string_types):
compose_files.append(compose_def)
else:
module.fail_json(msg="compose element '%s' must be a " +
"string or a dictionary" % compose_def)
before_stack_services = docker_stack_inspect(module, name)
rc, out, err = docker_stack_deploy(module, name, compose_files)
after_stack_services = docker_stack_inspect(module, name)
if rc != 0:
module.fail_json(msg="docker stack up deploy command failed",
rc=rc,
out=out, err=err, # Deprecated
stdout=out, stderr=err)
before_after_differences = json_diff(before_stack_services,
after_stack_services)
for k in before_after_differences.keys():
if isinstance(before_after_differences[k], dict):
before_after_differences[k].pop('UpdatedAt', None)
before_after_differences[k].pop('Version', None)
if not list(before_after_differences[k].keys()):
before_after_differences.pop(k)
if not before_after_differences:
module.exit_json(
changed=False,
rc=rc,
stdout=out,
stderr=err)
else:
module.exit_json(
changed=True,
rc=rc,
stdout=out,
stderr=err,
stack_spec_diff=json_diff(before_stack_services,
after_stack_services,
dump=True))
else:
if docker_stack_services(module, name):
rc, out, err = docker_stack_rm(module, name, absent_retries, absent_retries_interval)
if rc != 0:
module.fail_json(msg="'docker stack down' command failed",
rc=rc,
out=out, err=err, # Deprecated
stdout=out, stderr=err)
else:
module.exit_json(changed=True,
msg=out, rc=rc,
err=err, # Deprecated
stdout=out, stderr=err)
module.exit_json(changed=False)
if __name__ == "__main__":
main()

View file

@ -1,84 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2020 Jose Angel Munoz (@imjoseangel)
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = '''
---
module: docker_stack_info
author: "Jose Angel Munoz (@imjoseangel)"
short_description: Return information on a docker stack
description:
- Retrieve information on docker stacks using the C(docker stack) command
on the target node (see examples).
version_added: "1.0.0"
'''
RETURN = '''
results:
description: |
List of dictionaries containing the list of stacks or tasks associated
to a stack name.
sample: >
"results": [{"name":"grafana","namespace":"default","orchestrator":"Kubernetes","services":"2"}]
returned: always
type: list
'''
EXAMPLES = '''
- name: Shows stack info
community.general.docker_stack_info:
register: result
- name: Show results
ansible.builtin.debug:
var: result.results
'''
import json
from ansible.module_utils.basic import AnsibleModule
def docker_stack_list(module):
docker_bin = module.get_bin_path('docker', required=True)
rc, out, err = module.run_command(
[docker_bin, "stack", "ls", "--format={{json .}}"])
return rc, out.strip(), err.strip()
def main():
module = AnsibleModule(
argument_spec={
},
supports_check_mode=False
)
rc, out, err = docker_stack_list(module)
if rc != 0:
module.fail_json(msg="Error running docker stack. {0}".format(err),
rc=rc, stdout=out, stderr=err)
else:
if out:
ret = list(
json.loads(outitem)
for outitem in out.splitlines())
else:
ret = []
module.exit_json(changed=False,
rc=rc,
stdout=out,
stderr=err,
results=ret)
if __name__ == "__main__":
main()

View file

@ -1,95 +0,0 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2020 Jose Angel Munoz (@imjoseangel)
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
DOCUMENTATION = '''
---
module: docker_stack_task_info
author: "Jose Angel Munoz (@imjoseangel)"
short_description: Return information of the tasks on a docker stack
description:
- Retrieve information on docker stacks tasks using the C(docker stack) command
on the target node (see examples).
options:
name:
description:
- Stack name.
type: str
required: yes
version_added: "1.1.0"
'''
RETURN = '''
results:
description: |
List of dictionaries containing the list of tasks associated
to a stack name.
sample: >
[{"CurrentState":"Running","DesiredState":"Running","Error":"","ID":"7wqv6m02ugkw","Image":"busybox","Name":"test_stack.1","Node":"swarm","Ports":""}]
returned: always
type: list
elements: dict
'''
EXAMPLES = '''
- name: Shows stack info
community.general.docker_stack_task_info:
name: test_stack
register: result
- name: Show results
ansible.builtin.debug:
var: result.results
'''
import json
from ansible.module_utils.basic import AnsibleModule
def docker_stack_task(module, stack_name):
docker_bin = module.get_bin_path('docker', required=True)
rc, out, err = module.run_command(
[docker_bin, "stack", "ps", stack_name, "--format={{json .}}"])
return rc, out.strip(), err.strip()
def main():
module = AnsibleModule(
argument_spec={
'name': dict(type='str', required=True)
},
supports_check_mode=False
)
name = module.params['name']
rc, out, err = docker_stack_task(module, name)
if rc != 0:
module.fail_json(msg="Error running docker stack. {0}".format(err),
rc=rc, stdout=out, stderr=err)
else:
if out:
ret = list(
json.loads(outitem)
for outitem in out.splitlines())
else:
ret = []
module.exit_json(changed=False,
rc=rc,
stdout=out,
stderr=err,
results=ret)
if __name__ == "__main__":
main()

View file

@ -1,675 +0,0 @@
#!/usr/bin/python
# Copyright 2016 Red Hat | Ansible
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
---
module: docker_swarm
short_description: Manage Swarm cluster
description:
- Create a new Swarm cluster.
- Add/Remove nodes or managers to an existing cluster.
options:
advertise_addr:
description:
- Externally reachable address advertised to other nodes.
- This can either be an address/port combination
in the form C(192.168.1.1:4567), or an interface followed by a
port number, like C(eth0:4567).
- If the port number is omitted,
the port number from the listen address is used.
- If I(advertise_addr) is not specified, it will be automatically
detected when possible.
- Only used when swarm is initialised or joined. Because of this it's not
considered for idempotency checking.
type: str
default_addr_pool:
description:
- Default address pool in CIDR format.
- Only used when swarm is initialised. Because of this it's not considered
for idempotency checking.
- Requires API version >= 1.39.
type: list
elements: str
subnet_size:
description:
- Default address pool subnet mask length.
- Only used when swarm is initialised. Because of this it's not considered
for idempotency checking.
- Requires API version >= 1.39.
type: int
listen_addr:
description:
- Listen address used for inter-manager communication.
- This can either be an address/port combination in the form
C(192.168.1.1:4567), or an interface followed by a port number,
like C(eth0:4567).
- If the port number is omitted, the default swarm listening port
is used.
- Only used when swarm is initialised or joined. Because of this it's not
considered for idempotency checking.
type: str
default: 0.0.0.0:2377
force:
description:
- Use with state C(present) to force creating a new Swarm, even if already part of one.
- Use with state C(absent) to Leave the swarm even if this node is a manager.
type: bool
default: no
state:
description:
- Set to C(present), to create/update a new cluster.
- Set to C(join), to join an existing cluster.
- Set to C(absent), to leave an existing cluster.
- Set to C(remove), to remove an absent node from the cluster.
Note that removing requires Docker SDK for Python >= 2.4.0.
- Set to C(inspect) to display swarm informations.
type: str
default: present
choices:
- present
- join
- absent
- remove
- inspect
node_id:
description:
- Swarm id of the node to remove.
- Used with I(state=remove).
type: str
join_token:
description:
- Swarm token used to join a swarm cluster.
- Used with I(state=join).
type: str
remote_addrs:
description:
- Remote address of one or more manager nodes of an existing Swarm to connect to.
- Used with I(state=join).
type: list
elements: str
task_history_retention_limit:
description:
- Maximum number of tasks history stored.
- Docker default value is C(5).
type: int
snapshot_interval:
description:
- Number of logs entries between snapshot.
- Docker default value is C(10000).
type: int
keep_old_snapshots:
description:
- Number of snapshots to keep beyond the current snapshot.
- Docker default value is C(0).
type: int
log_entries_for_slow_followers:
description:
- Number of log entries to keep around to sync up slow followers after a snapshot is created.
type: int
heartbeat_tick:
description:
- Amount of ticks (in seconds) between each heartbeat.
- Docker default value is C(1s).
type: int
election_tick:
description:
- Amount of ticks (in seconds) needed without a leader to trigger a new election.
- Docker default value is C(10s).
type: int
dispatcher_heartbeat_period:
description:
- The delay for an agent to send a heartbeat to the dispatcher.
- Docker default value is C(5s).
type: int
node_cert_expiry:
description:
- Automatic expiry for nodes certificates.
- Docker default value is C(3months).
type: int
name:
description:
- The name of the swarm.
type: str
labels:
description:
- User-defined key/value metadata.
- Label operations in this module apply to the docker swarm cluster.
Use M(community.general.docker_node) module to add/modify/remove swarm node labels.
- Requires API version >= 1.32.
type: dict
signing_ca_cert:
description:
- The desired signing CA certificate for all swarm node TLS leaf certificates, in PEM format.
- This must not be a path to a certificate, but the contents of the certificate.
- Requires API version >= 1.30.
type: str
signing_ca_key:
description:
- The desired signing CA key for all swarm node TLS leaf certificates, in PEM format.
- This must not be a path to a key, but the contents of the key.
- Requires API version >= 1.30.
type: str
ca_force_rotate:
description:
- An integer whose purpose is to force swarm to generate a new signing CA certificate and key,
if none have been specified.
- Docker default value is C(0).
- Requires API version >= 1.30.
type: int
autolock_managers:
description:
- If set, generate a key and use it to lock data stored on the managers.
- Docker default value is C(no).
- M(community.general.docker_swarm_info) can be used to retrieve the unlock key.
type: bool
rotate_worker_token:
description: Rotate the worker join token.
type: bool
default: no
rotate_manager_token:
description: Rotate the manager join token.
type: bool
default: no
extends_documentation_fragment:
- community.general.docker
- community.general.docker.docker_py_1_documentation
requirements:
- "L(Docker SDK for Python,https://docker-py.readthedocs.io/en/stable/) >= 1.10.0 (use L(docker-py,https://pypi.org/project/docker-py/) for Python 2.6)"
- Docker API >= 1.25
author:
- Thierry Bouvet (@tbouvet)
- Piotr Wojciechowski (@WojciechowskiPiotr)
'''
EXAMPLES = '''
- name: Init a new swarm with default parameters
community.general.docker_swarm:
state: present
- name: Update swarm configuration
community.general.docker_swarm:
state: present
election_tick: 5
- name: Add nodes
community.general.docker_swarm:
state: join
advertise_addr: 192.168.1.2
join_token: SWMTKN-1--xxxxx
remote_addrs: [ '192.168.1.1:2377' ]
- name: Leave swarm for a node
community.general.docker_swarm:
state: absent
- name: Remove a swarm manager
community.general.docker_swarm:
state: absent
force: true
- name: Remove node from swarm
community.general.docker_swarm:
state: remove
node_id: mynode
- name: Inspect swarm
community.general.docker_swarm:
state: inspect
register: swarm_info
'''
RETURN = '''
swarm_facts:
description: Informations about swarm.
returned: success
type: dict
contains:
JoinTokens:
description: Tokens to connect to the Swarm.
returned: success
type: dict
contains:
Worker:
description: Token to create a new *worker* node
returned: success
type: str
example: SWMTKN-1--xxxxx
Manager:
description: Token to create a new *manager* node
returned: success
type: str
example: SWMTKN-1--xxxxx
UnlockKey:
description: The swarm unlock-key if I(autolock_managers) is C(true).
returned: on success if I(autolock_managers) is C(true)
and swarm is initialised, or if I(autolock_managers) has changed.
type: str
example: SWMKEY-1-xxx
actions:
description: Provides the actions done on the swarm.
returned: when action failed.
type: list
elements: str
example: "['This cluster is already a swarm cluster']"
'''
import json
import traceback
try:
from docker.errors import DockerException, APIError
except ImportError:
# missing Docker SDK for Python handled in ansible.module_utils.docker.common
pass
from ansible_collections.community.general.plugins.module_utils.docker.common import (
DockerBaseClass,
DifferenceTracker,
RequestException,
)
from ansible_collections.community.general.plugins.module_utils.docker.swarm import AnsibleDockerSwarmClient
from ansible.module_utils._text import to_native
class TaskParameters(DockerBaseClass):
def __init__(self):
super(TaskParameters, self).__init__()
self.advertise_addr = None
self.listen_addr = None
self.remote_addrs = None
self.join_token = None
# Spec
self.snapshot_interval = None
self.task_history_retention_limit = None
self.keep_old_snapshots = None
self.log_entries_for_slow_followers = None
self.heartbeat_tick = None
self.election_tick = None
self.dispatcher_heartbeat_period = None
self.node_cert_expiry = None
self.name = None
self.labels = None
self.log_driver = None
self.signing_ca_cert = None
self.signing_ca_key = None
self.ca_force_rotate = None
self.autolock_managers = None
self.rotate_worker_token = None
self.rotate_manager_token = None
self.default_addr_pool = None
self.subnet_size = None
@staticmethod
def from_ansible_params(client):
result = TaskParameters()
for key, value in client.module.params.items():
if key in result.__dict__:
setattr(result, key, value)
result.update_parameters(client)
return result
def update_from_swarm_info(self, swarm_info):
spec = swarm_info['Spec']
ca_config = spec.get('CAConfig') or dict()
if self.node_cert_expiry is None:
self.node_cert_expiry = ca_config.get('NodeCertExpiry')
if self.ca_force_rotate is None:
self.ca_force_rotate = ca_config.get('ForceRotate')
dispatcher = spec.get('Dispatcher') or dict()
if self.dispatcher_heartbeat_period is None:
self.dispatcher_heartbeat_period = dispatcher.get('HeartbeatPeriod')
raft = spec.get('Raft') or dict()
if self.snapshot_interval is None:
self.snapshot_interval = raft.get('SnapshotInterval')
if self.keep_old_snapshots is None:
self.keep_old_snapshots = raft.get('KeepOldSnapshots')
if self.heartbeat_tick is None:
self.heartbeat_tick = raft.get('HeartbeatTick')
if self.log_entries_for_slow_followers is None:
self.log_entries_for_slow_followers = raft.get('LogEntriesForSlowFollowers')
if self.election_tick is None:
self.election_tick = raft.get('ElectionTick')
orchestration = spec.get('Orchestration') or dict()
if self.task_history_retention_limit is None:
self.task_history_retention_limit = orchestration.get('TaskHistoryRetentionLimit')
encryption_config = spec.get('EncryptionConfig') or dict()
if self.autolock_managers is None:
self.autolock_managers = encryption_config.get('AutoLockManagers')
if self.name is None:
self.name = spec['Name']
if self.labels is None:
self.labels = spec.get('Labels') or {}
if 'LogDriver' in spec['TaskDefaults']:
self.log_driver = spec['TaskDefaults']['LogDriver']
def update_parameters(self, client):
assign = dict(
snapshot_interval='snapshot_interval',
task_history_retention_limit='task_history_retention_limit',
keep_old_snapshots='keep_old_snapshots',
log_entries_for_slow_followers='log_entries_for_slow_followers',
heartbeat_tick='heartbeat_tick',
election_tick='election_tick',
dispatcher_heartbeat_period='dispatcher_heartbeat_period',
node_cert_expiry='node_cert_expiry',
name='name',
labels='labels',
signing_ca_cert='signing_ca_cert',
signing_ca_key='signing_ca_key',
ca_force_rotate='ca_force_rotate',
autolock_managers='autolock_managers',
log_driver='log_driver',
)
params = dict()
for dest, source in assign.items():
if not client.option_minimal_versions[source]['supported']:
continue
value = getattr(self, source)
if value is not None:
params[dest] = value
self.spec = client.create_swarm_spec(**params)
def compare_to_active(self, other, client, differences):
for k in self.__dict__:
if k in ('advertise_addr', 'listen_addr', 'remote_addrs', 'join_token',
'rotate_worker_token', 'rotate_manager_token', 'spec',
'default_addr_pool', 'subnet_size'):
continue
if not client.option_minimal_versions[k]['supported']:
continue
value = getattr(self, k)
if value is None:
continue
other_value = getattr(other, k)
if value != other_value:
differences.add(k, parameter=value, active=other_value)
if self.rotate_worker_token:
differences.add('rotate_worker_token', parameter=True, active=False)
if self.rotate_manager_token:
differences.add('rotate_manager_token', parameter=True, active=False)
return differences
class SwarmManager(DockerBaseClass):
def __init__(self, client, results):
super(SwarmManager, self).__init__()
self.client = client
self.results = results
self.check_mode = self.client.check_mode
self.swarm_info = {}
self.state = client.module.params['state']
self.force = client.module.params['force']
self.node_id = client.module.params['node_id']
self.differences = DifferenceTracker()
self.parameters = TaskParameters.from_ansible_params(client)
self.created = False
def __call__(self):
choice_map = {
"present": self.init_swarm,
"join": self.join,
"absent": self.leave,
"remove": self.remove,
"inspect": self.inspect_swarm
}
if self.state == 'inspect':
self.client.module.deprecate(
"The 'inspect' state is deprecated, please use 'docker_swarm_info' to inspect swarm cluster",
version='2.0.0', collection_name='community.general') # was Ansible 2.12
choice_map.get(self.state)()
if self.client.module._diff or self.parameters.debug:
diff = dict()
diff['before'], diff['after'] = self.differences.get_before_after()
self.results['diff'] = diff
def inspect_swarm(self):
try:
data = self.client.inspect_swarm()
json_str = json.dumps(data, ensure_ascii=False)
self.swarm_info = json.loads(json_str)
self.results['changed'] = False
self.results['swarm_facts'] = self.swarm_info
unlock_key = self.get_unlock_key()
self.swarm_info.update(unlock_key)
except APIError:
return
def get_unlock_key(self):
default = {'UnlockKey': None}
if not self.has_swarm_lock_changed():
return default
try:
return self.client.get_unlock_key() or default
except APIError:
return default
def has_swarm_lock_changed(self):
return self.parameters.autolock_managers and (
self.created or self.differences.has_difference_for('autolock_managers')
)
def init_swarm(self):
if not self.force and self.client.check_if_swarm_manager():
self.__update_swarm()
return
if not self.check_mode:
init_arguments = {
'advertise_addr': self.parameters.advertise_addr,
'listen_addr': self.parameters.listen_addr,
'force_new_cluster': self.force,
'swarm_spec': self.parameters.spec,
}
if self.parameters.default_addr_pool is not None:
init_arguments['default_addr_pool'] = self.parameters.default_addr_pool
if self.parameters.subnet_size is not None:
init_arguments['subnet_size'] = self.parameters.subnet_size
try:
self.client.init_swarm(**init_arguments)
except APIError as exc:
self.client.fail("Can not create a new Swarm Cluster: %s" % to_native(exc))
if not self.client.check_if_swarm_manager():
if not self.check_mode:
self.client.fail("Swarm not created or other error!")
self.created = True
self.inspect_swarm()
self.results['actions'].append("New Swarm cluster created: %s" % (self.swarm_info.get('ID')))
self.differences.add('state', parameter='present', active='absent')
self.results['changed'] = True
self.results['swarm_facts'] = {
'JoinTokens': self.swarm_info.get('JoinTokens'),
'UnlockKey': self.swarm_info.get('UnlockKey')
}
def __update_swarm(self):
try:
self.inspect_swarm()
version = self.swarm_info['Version']['Index']
self.parameters.update_from_swarm_info(self.swarm_info)
old_parameters = TaskParameters()
old_parameters.update_from_swarm_info(self.swarm_info)
self.parameters.compare_to_active(old_parameters, self.client, self.differences)
if self.differences.empty:
self.results['actions'].append("No modification")
self.results['changed'] = False
return
update_parameters = TaskParameters.from_ansible_params(self.client)
update_parameters.update_parameters(self.client)
if not self.check_mode:
self.client.update_swarm(
version=version, swarm_spec=update_parameters.spec,
rotate_worker_token=self.parameters.rotate_worker_token,
rotate_manager_token=self.parameters.rotate_manager_token)
except APIError as exc:
self.client.fail("Can not update a Swarm Cluster: %s" % to_native(exc))
return
self.inspect_swarm()
self.results['actions'].append("Swarm cluster updated")
self.results['changed'] = True
def join(self):
if self.client.check_if_swarm_node():
self.results['actions'].append("This node is already part of a swarm.")
return
if not self.check_mode:
try:
self.client.join_swarm(
remote_addrs=self.parameters.remote_addrs, join_token=self.parameters.join_token,
listen_addr=self.parameters.listen_addr, advertise_addr=self.parameters.advertise_addr)
except APIError as exc:
self.client.fail("Can not join the Swarm Cluster: %s" % to_native(exc))
self.results['actions'].append("New node is added to swarm cluster")
self.differences.add('joined', parameter=True, active=False)
self.results['changed'] = True
def leave(self):
if not self.client.check_if_swarm_node():
self.results['actions'].append("This node is not part of a swarm.")
return
if not self.check_mode:
try:
self.client.leave_swarm(force=self.force)
except APIError as exc:
self.client.fail("This node can not leave the Swarm Cluster: %s" % to_native(exc))
self.results['actions'].append("Node has left the swarm cluster")
self.differences.add('joined', parameter='absent', active='present')
self.results['changed'] = True
def remove(self):
if not self.client.check_if_swarm_manager():
self.client.fail("This node is not a manager.")
try:
status_down = self.client.check_if_swarm_node_is_down(node_id=self.node_id, repeat_check=5)
except APIError:
return
if not status_down:
self.client.fail("Can not remove the node. The status node is ready and not down.")
if not self.check_mode:
try:
self.client.remove_node(node_id=self.node_id, force=self.force)
except APIError as exc:
self.client.fail("Can not remove the node from the Swarm Cluster: %s" % to_native(exc))
self.results['actions'].append("Node is removed from swarm cluster.")
self.differences.add('joined', parameter=False, active=True)
self.results['changed'] = True
def _detect_remove_operation(client):
return client.module.params['state'] == 'remove'
def main():
argument_spec = dict(
advertise_addr=dict(type='str'),
state=dict(type='str', default='present', choices=['present', 'join', 'absent', 'remove', 'inspect']),
force=dict(type='bool', default=False),
listen_addr=dict(type='str', default='0.0.0.0:2377'),
remote_addrs=dict(type='list', elements='str'),
join_token=dict(type='str'),
snapshot_interval=dict(type='int'),
task_history_retention_limit=dict(type='int'),
keep_old_snapshots=dict(type='int'),
log_entries_for_slow_followers=dict(type='int'),
heartbeat_tick=dict(type='int'),
election_tick=dict(type='int'),
dispatcher_heartbeat_period=dict(type='int'),
node_cert_expiry=dict(type='int'),
name=dict(type='str'),
labels=dict(type='dict'),
signing_ca_cert=dict(type='str'),
signing_ca_key=dict(type='str'),
ca_force_rotate=dict(type='int'),
autolock_managers=dict(type='bool'),
node_id=dict(type='str'),
rotate_worker_token=dict(type='bool', default=False),
rotate_manager_token=dict(type='bool', default=False),
default_addr_pool=dict(type='list', elements='str'),
subnet_size=dict(type='int'),
)
required_if = [
('state', 'join', ['remote_addrs', 'join_token']),
('state', 'remove', ['node_id'])
]
option_minimal_versions = dict(
labels=dict(docker_py_version='2.6.0', docker_api_version='1.32'),
signing_ca_cert=dict(docker_py_version='2.6.0', docker_api_version='1.30'),
signing_ca_key=dict(docker_py_version='2.6.0', docker_api_version='1.30'),
ca_force_rotate=dict(docker_py_version='2.6.0', docker_api_version='1.30'),
autolock_managers=dict(docker_py_version='2.6.0'),
log_driver=dict(docker_py_version='2.6.0'),
remove_operation=dict(
docker_py_version='2.4.0',
detect_usage=_detect_remove_operation,
usage_msg='remove swarm nodes'
),
default_addr_pool=dict(docker_py_version='4.0.0', docker_api_version='1.39'),
subnet_size=dict(docker_py_version='4.0.0', docker_api_version='1.39'),
)
client = AnsibleDockerSwarmClient(
argument_spec=argument_spec,
supports_check_mode=True,
required_if=required_if,
min_docker_version='1.10.0',
min_docker_api_version='1.25',
option_minimal_versions=option_minimal_versions,
)
try:
results = dict(
changed=False,
result='',
actions=[]
)
SwarmManager(client, results)()
client.module.exit_json(**results)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
except RequestException as e:
client.fail('An unexpected requests error occurred when docker-py tried to talk to the docker daemon: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':
main()

View file

@ -1,384 +0,0 @@
#!/usr/bin/python
#
# (c) 2019 Piotr Wojciechowski <piotr@it-playground.pl>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
---
module: docker_swarm_info
short_description: Retrieves facts about Docker Swarm cluster.
description:
- Retrieves facts about a Docker Swarm.
- Returns lists of swarm objects names for the services - nodes, services, tasks.
- The output differs depending on API version available on docker host.
- Must be run on Swarm Manager node; otherwise module fails with error message.
It does return boolean flags in on both error and success which indicate whether
the docker daemon can be communicated with, whether it is in Swarm mode, and
whether it is a Swarm Manager node.
author:
- Piotr Wojciechowski (@WojciechowskiPiotr)
options:
nodes:
description:
- Whether to list swarm nodes.
type: bool
default: no
nodes_filters:
description:
- A dictionary of filter values used for selecting nodes to list.
- "For example, C(name: mynode)."
- See L(the docker documentation,https://docs.docker.com/engine/reference/commandline/node_ls/#filtering)
for more information on possible filters.
type: dict
services:
description:
- Whether to list swarm services.
type: bool
default: no
services_filters:
description:
- A dictionary of filter values used for selecting services to list.
- "For example, C(name: myservice)."
- See L(the docker documentation,https://docs.docker.com/engine/reference/commandline/service_ls/#filtering)
for more information on possible filters.
type: dict
tasks:
description:
- Whether to list containers.
type: bool
default: no
tasks_filters:
description:
- A dictionary of filter values used for selecting tasks to list.
- "For example, C(node: mynode-1)."
- See L(the docker documentation,https://docs.docker.com/engine/reference/commandline/service_ps/#filtering)
for more information on possible filters.
type: dict
unlock_key:
description:
- Whether to retrieve the swarm unlock key.
type: bool
default: no
verbose_output:
description:
- When set to C(yes) and I(nodes), I(services) or I(tasks) is set to C(yes), then the module output will
contain verbose information about objects matching the full output of API method.
- For details see the documentation of your version of Docker API at U(https://docs.docker.com/engine/api/).
- The verbose output in this module contains only subset of information returned by I(_info) module
for each type of the objects.
type: bool
default: no
extends_documentation_fragment:
- community.general.docker
- community.general.docker.docker_py_1_documentation
requirements:
- "L(Docker SDK for Python,https://docker-py.readthedocs.io/en/stable/) >= 1.10.0 (use L(docker-py,https://pypi.org/project/docker-py/) for Python 2.6)"
- "Docker API >= 1.24"
'''
EXAMPLES = '''
- name: Get info on Docker Swarm
community.general.docker_swarm_info:
ignore_errors: yes
register: result
- name: Inform about basic flags
ansible.builtin.debug:
msg: |
Was able to talk to docker daemon: {{ result.can_talk_to_docker }}
Docker in Swarm mode: {{ result.docker_swarm_active }}
This is a Manager node: {{ result.docker_swarm_manager }}
- block:
- name: Get info on Docker Swarm and list of registered nodes
community.general.docker_swarm_info:
nodes: yes
register: result
- name: Get info on Docker Swarm and extended list of registered nodes
community.general.docker_swarm_info:
nodes: yes
verbose_output: yes
register: result
- name: Get info on Docker Swarm and filtered list of registered nodes
community.general.docker_swarm_info:
nodes: yes
nodes_filters:
name: mynode
register: result
- ansible.builtin.debug:
var: result.swarm_facts
- name: Get the swarm unlock key
community.general.docker_swarm_info:
unlock_key: yes
register: result
- ansible.builtin.debug:
var: result.swarm_unlock_key
'''
RETURN = '''
can_talk_to_docker:
description:
- Will be C(true) if the module can talk to the docker daemon.
returned: both on success and on error
type: bool
docker_swarm_active:
description:
- Will be C(true) if the module can talk to the docker daemon,
and the docker daemon is in Swarm mode.
returned: both on success and on error
type: bool
docker_swarm_manager:
description:
- Will be C(true) if the module can talk to the docker daemon,
the docker daemon is in Swarm mode, and the current node is
a manager node.
- Only if this one is C(true), the module will not fail.
returned: both on success and on error
type: bool
swarm_facts:
description:
- Facts representing the basic state of the docker Swarm cluster.
- Contains tokens to connect to the Swarm
returned: always
type: dict
swarm_unlock_key:
description:
- Contains the key needed to unlock the swarm.
returned: When I(unlock_key) is C(true).
type: str
nodes:
description:
- List of dict objects containing the basic information about each volume.
Keys matches the C(docker node ls) output unless I(verbose_output=yes).
See description for I(verbose_output).
returned: When I(nodes) is C(yes)
type: list
elements: dict
services:
description:
- List of dict objects containing the basic information about each volume.
Keys matches the C(docker service ls) output unless I(verbose_output=yes).
See description for I(verbose_output).
returned: When I(services) is C(yes)
type: list
elements: dict
tasks:
description:
- List of dict objects containing the basic information about each volume.
Keys matches the C(docker service ps) output unless I(verbose_output=yes).
See description for I(verbose_output).
returned: When I(tasks) is C(yes)
type: list
elements: dict
'''
import traceback
try:
from docker.errors import DockerException, APIError
except ImportError:
# missing Docker SDK for Python handled in ansible.module_utils.docker_common
pass
from ansible.module_utils._text import to_native
from ansible_collections.community.general.plugins.module_utils.docker.swarm import AnsibleDockerSwarmClient
from ansible_collections.community.general.plugins.module_utils.docker.common import (
DockerBaseClass,
clean_dict_booleans_for_docker_api,
RequestException,
)
class DockerSwarmManager(DockerBaseClass):
def __init__(self, client, results):
super(DockerSwarmManager, self).__init__()
self.client = client
self.results = results
self.verbose_output = self.client.module.params['verbose_output']
listed_objects = ['tasks', 'services', 'nodes']
self.client.fail_task_if_not_swarm_manager()
self.results['swarm_facts'] = self.get_docker_swarm_facts()
for docker_object in listed_objects:
if self.client.module.params[docker_object]:
returned_name = docker_object
filter_name = docker_object + "_filters"
filters = clean_dict_booleans_for_docker_api(client.module.params.get(filter_name))
self.results[returned_name] = self.get_docker_items_list(docker_object, filters)
if self.client.module.params['unlock_key']:
self.results['swarm_unlock_key'] = self.get_docker_swarm_unlock_key()
def get_docker_swarm_facts(self):
try:
return self.client.inspect_swarm()
except APIError as exc:
self.client.fail("Error inspecting docker swarm: %s" % to_native(exc))
def get_docker_items_list(self, docker_object=None, filters=None):
items = None
items_list = []
try:
if docker_object == 'nodes':
items = self.client.nodes(filters=filters)
elif docker_object == 'tasks':
items = self.client.tasks(filters=filters)
elif docker_object == 'services':
items = self.client.services(filters=filters)
except APIError as exc:
self.client.fail("Error inspecting docker swarm for object '%s': %s" %
(docker_object, to_native(exc)))
if self.verbose_output:
return items
for item in items:
item_record = dict()
if docker_object == 'nodes':
item_record = self.get_essential_facts_nodes(item)
elif docker_object == 'tasks':
item_record = self.get_essential_facts_tasks(item)
elif docker_object == 'services':
item_record = self.get_essential_facts_services(item)
if item_record['Mode'] == 'Global':
item_record['Replicas'] = len(items)
items_list.append(item_record)
return items_list
@staticmethod
def get_essential_facts_nodes(item):
object_essentials = dict()
object_essentials['ID'] = item.get('ID')
object_essentials['Hostname'] = item['Description']['Hostname']
object_essentials['Status'] = item['Status']['State']
object_essentials['Availability'] = item['Spec']['Availability']
if 'ManagerStatus' in item:
object_essentials['ManagerStatus'] = item['ManagerStatus']['Reachability']
if 'Leader' in item['ManagerStatus'] and item['ManagerStatus']['Leader'] is True:
object_essentials['ManagerStatus'] = "Leader"
else:
object_essentials['ManagerStatus'] = None
object_essentials['EngineVersion'] = item['Description']['Engine']['EngineVersion']
return object_essentials
def get_essential_facts_tasks(self, item):
object_essentials = dict()
object_essentials['ID'] = item['ID']
# Returning container ID to not trigger another connection to host
# Container ID is sufficient to get extended info in other tasks
object_essentials['ContainerID'] = item['Status']['ContainerStatus']['ContainerID']
object_essentials['Image'] = item['Spec']['ContainerSpec']['Image']
object_essentials['Node'] = self.client.get_node_name_by_id(item['NodeID'])
object_essentials['DesiredState'] = item['DesiredState']
object_essentials['CurrentState'] = item['Status']['State']
if 'Err' in item['Status']:
object_essentials['Error'] = item['Status']['Err']
else:
object_essentials['Error'] = None
return object_essentials
@staticmethod
def get_essential_facts_services(item):
object_essentials = dict()
object_essentials['ID'] = item['ID']
object_essentials['Name'] = item['Spec']['Name']
if 'Replicated' in item['Spec']['Mode']:
object_essentials['Mode'] = "Replicated"
object_essentials['Replicas'] = item['Spec']['Mode']['Replicated']['Replicas']
elif 'Global' in item['Spec']['Mode']:
object_essentials['Mode'] = "Global"
# Number of replicas have to be updated in calling method or may be left as None
object_essentials['Replicas'] = None
object_essentials['Image'] = item['Spec']['TaskTemplate']['ContainerSpec']['Image']
if 'Ports' in item['Spec']['EndpointSpec']:
object_essentials['Ports'] = item['Spec']['EndpointSpec']['Ports']
else:
object_essentials['Ports'] = []
return object_essentials
def get_docker_swarm_unlock_key(self):
unlock_key = self.client.get_unlock_key() or {}
return unlock_key.get('UnlockKey') or None
def main():
argument_spec = dict(
nodes=dict(type='bool', default=False),
nodes_filters=dict(type='dict'),
tasks=dict(type='bool', default=False),
tasks_filters=dict(type='dict'),
services=dict(type='bool', default=False),
services_filters=dict(type='dict'),
unlock_key=dict(type='bool', default=False),
verbose_output=dict(type='bool', default=False),
)
option_minimal_versions = dict(
unlock_key=dict(docker_py_version='2.7.0', docker_api_version='1.25'),
)
client = AnsibleDockerSwarmClient(
argument_spec=argument_spec,
supports_check_mode=True,
min_docker_version='1.10.0',
min_docker_api_version='1.24',
option_minimal_versions=option_minimal_versions,
fail_results=dict(
can_talk_to_docker=False,
docker_swarm_active=False,
docker_swarm_manager=False,
),
)
client.fail_results['can_talk_to_docker'] = True
client.fail_results['docker_swarm_active'] = client.check_if_swarm_node()
client.fail_results['docker_swarm_manager'] = client.check_if_swarm_manager()
try:
results = dict(
changed=False,
)
DockerSwarmManager(client, results)
results.update(client.fail_results)
client.module.exit_json(**results)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
except RequestException as e:
client.fail('An unexpected requests error occurred when docker-py tried to talk to the docker daemon: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':
main()

File diff suppressed because it is too large Load diff

View file

@ -1,115 +0,0 @@
#!/usr/bin/python
#
# (c) 2019 Hannes Ljungberg <hannes.ljungberg@gmail.com>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
---
module: docker_swarm_service_info
short_description: Retrieves information about docker services from a Swarm Manager
description:
- Retrieves information about a docker service.
- Essentially returns the output of C(docker service inspect <name>).
- Must be executed on a host running as Swarm Manager, otherwise the module will fail.
options:
name:
description:
- The name of the service to inspect.
type: str
required: yes
extends_documentation_fragment:
- community.general.docker
- community.general.docker.docker_py_1_documentation
author:
- Hannes Ljungberg (@hannseman)
requirements:
- "L(Docker SDK for Python,https://docker-py.readthedocs.io/en/stable/) >= 2.0.0"
- "Docker API >= 1.24"
'''
EXAMPLES = '''
- name: Get info from a service
community.general.docker_swarm_service_info:
name: myservice
register: result
'''
RETURN = '''
exists:
description:
- Returns whether the service exists.
type: bool
returned: always
sample: true
service:
description:
- A dictionary representing the current state of the service. Matches the C(docker service inspect) output.
- Will be C(none) if service does not exist.
returned: always
type: dict
'''
import traceback
try:
from docker.errors import DockerException
except ImportError:
# missing Docker SDK for Python handled in ansible.module_utils.docker.common
pass
from ansible_collections.community.general.plugins.module_utils.docker.common import (
RequestException,
)
from ansible_collections.community.general.plugins.module_utils.docker.swarm import AnsibleDockerSwarmClient
def get_service_info(client):
service = client.module.params['name']
return client.get_service_inspect(
service_id=service,
skip_missing=True
)
def main():
argument_spec = dict(
name=dict(type='str', required=True),
)
client = AnsibleDockerSwarmClient(
argument_spec=argument_spec,
supports_check_mode=True,
min_docker_version='2.0.0',
min_docker_api_version='1.24',
)
client.fail_task_if_not_swarm_manager()
try:
service = get_service_info(client)
client.module.exit_json(
changed=False,
service=service,
exists=bool(service)
)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
except RequestException as e:
client.fail('An unexpected requests error occurred when docker-py tried to talk to the docker daemon: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':
main()

View file

@ -1,332 +0,0 @@
#!/usr/bin/python
# coding: utf-8
#
# Copyright 2017 Red Hat | Ansible, Alex Grönholm <alex.gronholm@nextday.fi>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
module: docker_volume
short_description: Manage Docker volumes
description:
- Create/remove Docker volumes.
- Performs largely the same function as the "docker volume" CLI subcommand.
options:
volume_name:
description:
- Name of the volume to operate on.
type: str
required: yes
aliases:
- name
driver:
description:
- Specify the type of volume. Docker provides the C(local) driver, but 3rd party drivers can also be used.
type: str
default: local
driver_options:
description:
- "Dictionary of volume settings. Consult docker docs for valid options and values:
U(https://docs.docker.com/engine/reference/commandline/volume_create/#driver-specific-options)"
type: dict
labels:
description:
- Dictionary of label key/values to set for the volume
type: dict
force:
description:
- With state C(present) causes the volume to be deleted and recreated if the volume already
exist and the driver, driver options or labels differ. This will cause any data in the existing
volume to be lost.
- Deprecated. Will be removed in community.general 2.0.0. Set I(recreate) to C(options-changed) instead
for the same behavior of setting I(force) to C(yes).
type: bool
recreate:
description:
- Controls when a volume will be recreated when I(state) is C(present). Please
note that recreating an existing volume will cause **any data in the existing volume
to be lost!** The volume will be deleted and a new volume with the same name will be
created.
- The value C(always) forces the volume to be always recreated.
- The value C(never) makes sure the volume will not be recreated.
- The value C(options-changed) makes sure the volume will be recreated if the volume
already exist and the driver, driver options or labels differ.
type: str
default: never
choices:
- always
- never
- options-changed
state:
description:
- C(absent) deletes the volume.
- C(present) creates the volume, if it does not already exist.
type: str
default: present
choices:
- absent
- present
extends_documentation_fragment:
- community.general.docker
- community.general.docker.docker_py_1_documentation
author:
- Alex Grönholm (@agronholm)
requirements:
- "L(Docker SDK for Python,https://docker-py.readthedocs.io/en/stable/) >= 1.10.0 (use L(docker-py,https://pypi.org/project/docker-py/) for Python 2.6)"
- "The docker server >= 1.9.0"
'''
EXAMPLES = '''
- name: Create a volume
community.general.docker_volume:
name: volume_one
- name: Remove a volume
community.general.docker_volume:
name: volume_one
state: absent
- name: Create a volume with options
community.general.docker_volume:
name: volume_two
driver_options:
type: btrfs
device: /dev/sda2
'''
RETURN = '''
volume:
description:
- Volume inspection results for the affected volume.
- Note that facts are part of the registered vars since Ansible 2.8. For compatibility reasons, the facts
are also accessible directly as C(docker_volume). Note that the returned fact will be removed in community.general 2.0.0.
returned: success
type: dict
sample: {}
'''
import traceback
try:
from docker.errors import DockerException, APIError
except ImportError:
# missing Docker SDK for Python handled in ansible.module_utils.docker.common
pass
from ansible_collections.community.general.plugins.module_utils.docker.common import (
DockerBaseClass,
AnsibleDockerClient,
DifferenceTracker,
RequestException,
)
from ansible.module_utils.six import iteritems, text_type
class TaskParameters(DockerBaseClass):
def __init__(self, client):
super(TaskParameters, self).__init__()
self.client = client
self.volume_name = None
self.driver = None
self.driver_options = None
self.labels = None
self.force = None
self.recreate = None
self.debug = None
for key, value in iteritems(client.module.params):
setattr(self, key, value)
if self.force is not None:
if self.recreate != 'never':
client.fail('Cannot use the deprecated "force" '
'option when "recreate" is set. Please stop '
'using the force option.')
client.module.warn('The "force" option of docker_volume has been deprecated '
'in Ansible 2.8. Please use the "recreate" '
'option, which provides the same functionality as "force".')
self.recreate = 'options-changed' if self.force else 'never'
class DockerVolumeManager(object):
def __init__(self, client):
self.client = client
self.parameters = TaskParameters(client)
self.check_mode = self.client.check_mode
self.results = {
u'changed': False,
u'actions': []
}
self.diff = self.client.module._diff
self.diff_tracker = DifferenceTracker()
self.diff_result = dict()
self.existing_volume = self.get_existing_volume()
state = self.parameters.state
if state == 'present':
self.present()
elif state == 'absent':
self.absent()
if self.diff or self.check_mode or self.parameters.debug:
if self.diff:
self.diff_result['before'], self.diff_result['after'] = self.diff_tracker.get_before_after()
self.results['diff'] = self.diff_result
def get_existing_volume(self):
try:
volumes = self.client.volumes()
except APIError as e:
self.client.fail(text_type(e))
if volumes[u'Volumes'] is None:
return None
for volume in volumes[u'Volumes']:
if volume['Name'] == self.parameters.volume_name:
return volume
return None
def has_different_config(self):
"""
Return the list of differences between the current parameters and the existing volume.
:return: list of options that differ
"""
differences = DifferenceTracker()
if self.parameters.driver and self.parameters.driver != self.existing_volume['Driver']:
differences.add('driver', parameter=self.parameters.driver, active=self.existing_volume['Driver'])
if self.parameters.driver_options:
if not self.existing_volume.get('Options'):
differences.add('driver_options',
parameter=self.parameters.driver_options,
active=self.existing_volume.get('Options'))
else:
for key, value in iteritems(self.parameters.driver_options):
if (not self.existing_volume['Options'].get(key) or
value != self.existing_volume['Options'][key]):
differences.add('driver_options.%s' % key,
parameter=value,
active=self.existing_volume['Options'].get(key))
if self.parameters.labels:
existing_labels = self.existing_volume.get('Labels', {})
for label in self.parameters.labels:
if existing_labels.get(label) != self.parameters.labels.get(label):
differences.add('labels.%s' % label,
parameter=self.parameters.labels.get(label),
active=existing_labels.get(label))
return differences
def create_volume(self):
if not self.existing_volume:
if not self.check_mode:
try:
params = dict(
driver=self.parameters.driver,
driver_opts=self.parameters.driver_options,
)
if self.parameters.labels is not None:
params['labels'] = self.parameters.labels
resp = self.client.create_volume(self.parameters.volume_name, **params)
self.existing_volume = self.client.inspect_volume(resp['Name'])
except APIError as e:
self.client.fail(text_type(e))
self.results['actions'].append("Created volume %s with driver %s" % (self.parameters.volume_name, self.parameters.driver))
self.results['changed'] = True
def remove_volume(self):
if self.existing_volume:
if not self.check_mode:
try:
self.client.remove_volume(self.parameters.volume_name)
except APIError as e:
self.client.fail(text_type(e))
self.results['actions'].append("Removed volume %s" % self.parameters.volume_name)
self.results['changed'] = True
def present(self):
differences = DifferenceTracker()
if self.existing_volume:
differences = self.has_different_config()
self.diff_tracker.add('exists', parameter=True, active=self.existing_volume is not None)
if (not differences.empty and self.parameters.recreate == 'options-changed') or self.parameters.recreate == 'always':
self.remove_volume()
self.existing_volume = None
self.create_volume()
if self.diff or self.check_mode or self.parameters.debug:
self.diff_result['differences'] = differences.get_legacy_docker_diffs()
self.diff_tracker.merge(differences)
if not self.check_mode and not self.parameters.debug:
self.results.pop('actions')
volume_facts = self.get_existing_volume()
self.results['ansible_facts'] = {u'docker_volume': volume_facts}
self.results['volume'] = volume_facts
def absent(self):
self.diff_tracker.add('exists', parameter=False, active=self.existing_volume is not None)
self.remove_volume()
def main():
argument_spec = dict(
volume_name=dict(type='str', required=True, aliases=['name']),
state=dict(type='str', default='present', choices=['present', 'absent']),
driver=dict(type='str', default='local'),
driver_options=dict(type='dict', default={}),
labels=dict(type='dict'),
force=dict(type='bool', removed_in_version='2.0.0', removed_from_collection='community.general'), # was Ansible 2.12
recreate=dict(type='str', default='never', choices=['always', 'never', 'options-changed']),
debug=dict(type='bool', default=False)
)
option_minimal_versions = dict(
labels=dict(docker_py_version='1.10.0', docker_api_version='1.23'),
)
client = AnsibleDockerClient(
argument_spec=argument_spec,
supports_check_mode=True,
min_docker_version='1.10.0',
min_docker_api_version='1.21',
# "The docker server >= 1.9.0"
option_minimal_versions=option_minimal_versions,
)
try:
cm = DockerVolumeManager(client)
client.module.exit_json(**cm.results)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
except RequestException as e:
client.fail('An unexpected requests error occurred when docker-py tried to talk to the docker daemon: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':
main()

View file

@ -1,128 +0,0 @@
#!/usr/bin/python
# coding: utf-8
#
# Copyright 2017 Red Hat | Ansible, Alex Grönholm <alex.gronholm@nextday.fi>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = '''
module: docker_volume_info
short_description: Retrieve facts about Docker volumes
description:
- Performs largely the same function as the "docker volume inspect" CLI subcommand.
options:
name:
description:
- Name of the volume to inspect.
type: str
required: yes
aliases:
- volume_name
extends_documentation_fragment:
- community.general.docker
- community.general.docker.docker_py_1_documentation
author:
- Felix Fontein (@felixfontein)
requirements:
- "L(Docker SDK for Python,https://docker-py.readthedocs.io/en/stable/) >= 1.8.0 (use L(docker-py,https://pypi.org/project/docker-py/) for Python 2.6)"
- "Docker API >= 1.21"
'''
EXAMPLES = '''
- name: Get infos on volume
community.general.docker_volume_info:
name: mydata
register: result
- name: Does volume exist?
ansible.builtin.debug:
msg: "The volume {{ 'exists' if result.exists else 'does not exist' }}"
- name: Print information about volume
ansible.builtin.debug:
var: result.volume
when: result.exists
'''
RETURN = '''
exists:
description:
- Returns whether the volume exists.
type: bool
returned: always
sample: true
volume:
description:
- Volume inspection results for the affected volume.
- Will be C(none) if volume does not exist.
returned: success
type: dict
sample: '{
"CreatedAt": "2018-12-09T17:43:44+01:00",
"Driver": "local",
"Labels": null,
"Mountpoint": "/var/lib/docker/volumes/ansible-test-bd3f6172/_data",
"Name": "ansible-test-bd3f6172",
"Options": {},
"Scope": "local"
}'
'''
import traceback
try:
from docker.errors import DockerException, NotFound
except ImportError:
# missing Docker SDK for Python handled in ansible.module_utils.docker.common
pass
from ansible_collections.community.general.plugins.module_utils.docker.common import (
AnsibleDockerClient,
RequestException,
)
def get_existing_volume(client, volume_name):
try:
return client.inspect_volume(volume_name)
except NotFound as dummy:
return None
except Exception as exc:
client.fail("Error inspecting volume: %s" % exc)
def main():
argument_spec = dict(
name=dict(type='str', required=True, aliases=['volume_name']),
)
client = AnsibleDockerClient(
argument_spec=argument_spec,
supports_check_mode=True,
min_docker_version='1.8.0',
min_docker_api_version='1.21',
)
try:
volume = get_existing_volume(client, client.module.params['name'])
client.module.exit_json(
changed=False,
exists=(True if volume else False),
volume=volume,
)
except DockerException as e:
client.fail('An unexpected docker error occurred: {0}'.format(e), exception=traceback.format_exc())
except RequestException as e:
client.fail('An unexpected requests error occurred when docker-py tried to talk to the docker daemon: {0}'.format(e), exception=traceback.format_exc())
if __name__ == '__main__':
main()

View file

@ -1 +0,0 @@
./cloud/docker/docker_compose.py

View file

@ -1 +0,0 @@
./cloud/docker/docker_config.py

View file

@ -1 +0,0 @@
./cloud/docker/docker_container.py

View file

@ -1 +0,0 @@
./cloud/docker/docker_container_info.py

View file

@ -1 +0,0 @@
./cloud/docker/docker_host_info.py

View file

@ -1 +0,0 @@
./cloud/docker/docker_image.py

View file

@ -1 +0,0 @@
cloud/docker/docker_image_facts.py

View file

@ -1 +0,0 @@
./cloud/docker/docker_image_info.py

View file

@ -1 +0,0 @@
./cloud/docker/docker_login.py

View file

@ -1 +0,0 @@
./cloud/docker/docker_network.py

View file

@ -1 +0,0 @@
./cloud/docker/docker_network_info.py

View file

@ -1 +0,0 @@
./cloud/docker/docker_node.py

View file

@ -1 +0,0 @@
./cloud/docker/docker_node_info.py

View file

@ -1 +0,0 @@
./cloud/docker/docker_prune.py

View file

@ -1 +0,0 @@
./cloud/docker/docker_secret.py

View file

@ -1 +0,0 @@
cloud/docker/docker_service.py

View file

@ -1 +0,0 @@
./cloud/docker/docker_stack.py

View file

@ -1 +0,0 @@
./cloud/docker/docker_stack_info.py

View file

@ -1 +0,0 @@
./cloud/docker/docker_stack_task_info.py

View file

@ -1 +0,0 @@
./cloud/docker/docker_swarm.py

View file

@ -1 +0,0 @@
./cloud/docker/docker_swarm_info.py

View file

@ -1 +0,0 @@
./cloud/docker/docker_swarm_service.py

View file

@ -1 +0,0 @@
./cloud/docker/docker_swarm_service_info.py

View file

@ -1 +0,0 @@
./cloud/docker/docker_volume.py

View file

@ -1 +0,0 @@
./cloud/docker/docker_volume_info.py

View file

@ -1,3 +0,0 @@
disabled # tests already running in community.docker
non_local
unsupported

View file

@ -1 +0,0 @@
../connection_posix/test.sh

View file

@ -1,6 +0,0 @@
[docker]
docker-pipelining ansible_ssh_pipelining=true
docker-no-pipelining ansible_ssh_pipelining=false
[docker:vars]
ansible_host=ubuntu-latest
ansible_connection=community.general.docker

View file

@ -1,10 +0,0 @@
disabled # tests already running in community.docker
shippable/posix/group3
skip/osx
skip/macos
skip/freebsd
skip/aix
destructive
skip/docker # The tests sometimes make docker daemon unstable; hence,
# we skip all docker-based CI runs to avoid disrupting
# the whole CI system.

View file

@ -1,3 +0,0 @@
---
dependencies:
- setup_docker

View file

@ -1,11 +0,0 @@
---
####################################################################
# WARNING: These are designed specifically for Ansible tests #
# and should not be used as examples of how to write Ansible roles #
####################################################################
- include_tasks: test_docker_config.yml
when: docker_py_version is version('2.6.0', '>=') and docker_api_version is version('1.30', '>=')
- fail: msg="Too old docker / docker-py version to run docker_config tests!"
when: not(docker_py_version is version('2.6.0', '>=') and docker_api_version is version('1.30', '>=')) and (ansible_distribution != 'CentOS' or ansible_distribution_major_version|int > 6)

View file

@ -1,139 +0,0 @@
---
- block:
- shell: "docker info --format '{% raw %}{{json .}}{% endraw %}' | python -m json.tool"
- name: Make sure we're not already using Docker swarm
docker_swarm:
state: absent
force: true
- shell: "docker info --format '{% raw %}{{json .}}{% endraw %}' | python -m json.tool"
- name: Create a Swarm cluster
docker_swarm:
state: present
advertise_addr: "{{ansible_default_ipv4.address}}"
- name: Parameter name should be required
docker_config:
state: present
ignore_errors: yes
register: output
- name: assert failure when called with no name
assert:
that:
- 'output.failed'
- 'output.msg == "missing required arguments: name"'
- name: Test parameters
docker_config:
name: foo
state: present
ignore_errors: yes
register: output
- name: assert failure when called with no data
assert:
that:
- 'output.failed'
- 'output.msg == "state is present but all of the following are missing: data"'
- name: Create config
docker_config:
name: db_password
data: opensesame!
state: present
register: output
- name: Create variable config_id
set_fact:
config_id: "{{ output.config_id }}"
- name: Inspect config
command: "docker config inspect {{ config_id }}"
register: inspect
ignore_errors: yes
- debug: var=inspect
- name: assert config creation succeeded
assert:
that:
- "'db_password' in inspect.stdout"
- "'ansible_key' in inspect.stdout"
when: inspect is not failed
- assert:
that:
- "'is too new. Maximum supported API version is' in inspect.stderr"
when: inspect is failed
- name: Create config again
docker_config:
name: db_password
data: opensesame!
state: present
register: output
- name: assert create config is idempotent
assert:
that:
- not output.changed
- name: Create config again (base64)
docker_config:
name: db_password
data: b3BlbnNlc2FtZSE=
data_is_b64: true
state: present
register: output
- name: assert create config (base64) is idempotent
assert:
that:
- not output.changed
- name: Update config
docker_config:
name: db_password
data: newpassword!
state: present
register: output
- name: assert config was updated
assert:
that:
- output.changed
- output.config_id != config_id
- name: Remove config
docker_config:
name: db_password
state: absent
- name: Check that config is removed
command: "docker config inspect {{ config_id }}"
register: output
ignore_errors: yes
- name: assert config was removed
assert:
that:
- output.failed
- name: Remove config
docker_config:
name: db_password
state: absent
register: output
- name: assert remove config is idempotent
assert:
that:
- not output.changed
always:
- name: Remove a Swarm cluster
docker_swarm:
state: absent
force: true

View file

@ -1,7 +0,0 @@
disabled # tests already running in community.docker
shippable/posix/group5
skip/osx
skip/macos
skip/freebsd
skip/aix
destructive

View file

@ -1,2 +0,0 @@
TEST3=val3
TEST4=val4

View file

@ -1,34 +0,0 @@
# (c) 2020, Felix Fontein <felix@fontein.de>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from ansible_collections.ansible.netcommon.plugins.module_utils.compat import ipaddress
def _normalize_ipaddr(ipaddr):
return ipaddress.ip_address(ipaddr).compressed
class FilterModule(object):
""" IP address and network manipulation filters """
def filters(self):
return {
'normalize_ipaddr': _normalize_ipaddr,
}

View file

@ -1,3 +0,0 @@
---
dependencies:
- setup_docker

View file

@ -1,43 +0,0 @@
---
####################################################################
# WARNING: These are designed specifically for Ansible tests #
# and should not be used as examples of how to write Ansible roles #
####################################################################
# Create random name prefix (for containers, networks, ...)
- name: Create random container name prefix
set_fact:
cname_prefix: "{{ 'ansible-test-%0x' % ((2**32) | random) }}"
cnames: []
dnetworks: []
- debug:
msg: "Using container name prefix {{ cname_prefix }}"
# Run the tests
- block:
- include_tasks: run-test.yml
with_fileglob:
- "tests/*.yml"
always:
- name: "Make sure all containers are removed"
docker_container:
name: "{{ item }}"
state: absent
force_kill: yes
with_items: "{{ cnames }}"
diff: no
- name: "Make sure all networks are removed"
docker_network:
name: "{{ item }}"
state: absent
force: yes
with_items: "{{ dnetworks }}"
when: docker_py_version is version('1.10.0', '>=')
diff: no
when: docker_py_version is version('1.8.0', '>=') and docker_api_version is version('1.20', '>=')
- fail: msg="Too old docker / docker-py version to run all docker_container tests!"
when: not(docker_py_version is version('3.5.0', '>=') and docker_api_version is version('1.25', '>=')) and (ansible_distribution != 'CentOS' or ansible_distribution_major_version|int > 6)

View file

@ -1,3 +0,0 @@
---
- name: "Loading tasks from {{ item }}"
include_tasks: "{{ item }}"

View file

@ -1,463 +0,0 @@
---
- name: Registering container name
set_fact:
cname: "{{ cname_prefix ~ '-comparisons' }}"
- name: Registering container name
set_fact:
cnames: "{{ cnames + [cname] }}"
####################################################################
## value ###########################################################
####################################################################
- name: value
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
hostname: example.com
register: value_1
- name: value (change, ignore)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
hostname: example.org
force_kill: yes
comparisons:
hostname: ignore
register: value_2
- name: value (change, strict)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
hostname: example.org
force_kill: yes
comparisons:
hostname: strict
register: value_3
- name: cleanup
docker_container:
name: "{{ cname }}"
state: absent
force_kill: yes
diff: no
- assert:
that:
- value_1 is changed
- value_2 is not changed
- value_3 is changed
####################################################################
## list ############################################################
####################################################################
- name: list
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
dns_servers:
- 1.1.1.1
- 8.8.8.8
register: list_1
- name: list (change, ignore)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
dns_servers:
- 9.9.9.9
force_kill: yes
comparisons:
dns_servers: ignore
register: list_2
- name: list (change, strict)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
dns_servers:
- 9.9.9.9
force_kill: yes
comparisons:
dns_servers: strict
register: list_3
- name: cleanup
docker_container:
name: "{{ cname }}"
state: absent
force_kill: yes
diff: no
- assert:
that:
- list_1 is changed
- list_2 is not changed
- list_3 is changed
####################################################################
## set #############################################################
####################################################################
- name: set
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
groups:
- "1010"
- "1011"
register: set_1
- name: set (change, ignore)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
groups:
- "1010"
- "1011"
- "1012"
force_kill: yes
comparisons:
groups: ignore
register: set_2
- name: set (change, allow_more_present)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
groups:
- "1010"
- "1011"
- "1012"
force_kill: yes
comparisons:
groups: allow_more_present
register: set_3
- name: set (change, allow_more_present)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
groups:
- "1010"
- "1012"
force_kill: yes
comparisons:
groups: allow_more_present
register: set_4
- name: set (change, strict)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
groups:
- "1010"
- "1012"
force_kill: yes
comparisons:
groups: strict
register: set_5
- name: cleanup
docker_container:
name: "{{ cname }}"
state: absent
force_kill: yes
diff: no
- assert:
that:
- set_1 is changed
- set_2 is not changed
- set_3 is changed
- set_4 is not changed
- set_5 is changed
####################################################################
## set(dict) #######################################################
####################################################################
- name: set(dict)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
devices:
- "/dev/random:/dev/virt-random:rwm"
- "/dev/urandom:/dev/virt-urandom:rwm"
register: set_dict_1
- name: set(dict) (change, ignore)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
devices:
- "/dev/random:/dev/virt-random:rwm"
- "/dev/urandom:/dev/virt-urandom:rwm"
- "/dev/null:/dev/virt-null:rwm"
force_kill: yes
comparisons:
devices: ignore
register: set_dict_2
- name: set(dict) (change, allow_more_present)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
devices:
- "/dev/random:/dev/virt-random:rwm"
- "/dev/urandom:/dev/virt-urandom:rwm"
- "/dev/null:/dev/virt-null:rwm"
force_kill: yes
comparisons:
devices: allow_more_present
register: set_dict_3
- name: set(dict) (change, allow_more_present)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
devices:
- "/dev/random:/dev/virt-random:rwm"
- "/dev/null:/dev/virt-null:rwm"
force_kill: yes
comparisons:
devices: allow_more_present
register: set_dict_4
- name: set(dict) (change, strict)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
devices:
- "/dev/random:/dev/virt-random:rwm"
- "/dev/null:/dev/virt-null:rwm"
force_kill: yes
comparisons:
devices: strict
register: set_dict_5
- name: cleanup
docker_container:
name: "{{ cname }}"
state: absent
force_kill: yes
diff: no
- assert:
that:
- set_dict_1 is changed
- set_dict_2 is not changed
- set_dict_3 is changed
- set_dict_4 is not changed
- set_dict_5 is changed
####################################################################
## dict ############################################################
####################################################################
- name: dict
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
labels:
ansible.test.1: hello
ansible.test.2: world
register: dict_1
- name: dict (change, ignore)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
labels:
ansible.test.1: hello
ansible.test.2: world
ansible.test.3: ansible
force_kill: yes
comparisons:
labels: ignore
register: dict_2
- name: dict (change, allow_more_present)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
labels:
ansible.test.1: hello
ansible.test.2: world
ansible.test.3: ansible
force_kill: yes
comparisons:
labels: allow_more_present
register: dict_3
- name: dict (change, allow_more_present)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
labels:
ansible.test.1: hello
ansible.test.3: ansible
force_kill: yes
comparisons:
labels: allow_more_present
register: dict_4
- name: dict (change, strict)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
labels:
ansible.test.1: hello
ansible.test.3: ansible
force_kill: yes
comparisons:
labels: strict
register: dict_5
- name: cleanup
docker_container:
name: "{{ cname }}"
state: absent
force_kill: yes
diff: no
- assert:
that:
- dict_1 is changed
- dict_2 is not changed
- dict_3 is changed
- dict_4 is not changed
- dict_5 is changed
####################################################################
## wildcard ########################################################
####################################################################
- name: Pull hello-world image to make sure wildcard_2 test succeeds
# If the image isn't there, it will pull it and return 'changed'.
docker_image:
name: hello-world
pull: true
- name: wildcard
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
hostname: example.com
stop_timeout: 1
labels:
ansible.test.1: hello
ansible.test.2: world
ansible.test.3: ansible
register: wildcard_1
- name: wildcard (change, ignore)
docker_container:
image: hello-world
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
hostname: example.org
stop_timeout: 2
labels:
ansible.test.1: hello
ansible.test.4: ignore
force_kill: yes
comparisons:
'*': ignore
register: wildcard_2
- name: wildcard (change, strict)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
hostname: example.org
stop_timeout: 1
labels:
ansible.test.1: hello
ansible.test.2: world
ansible.test.3: ansible
force_kill: yes
comparisons:
'*': strict
register: wildcard_3
- name: wildcard (no change, strict)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
hostname: example.org
stop_timeout: 1
labels:
ansible.test.1: hello
ansible.test.2: world
ansible.test.3: ansible
force_kill: yes
comparisons:
'*': strict
register: wildcard_4
- name: cleanup
docker_container:
name: "{{ cname }}"
state: absent
force_kill: yes
diff: no
- assert:
that:
- wildcard_1 is changed
- wildcard_2 is not changed
- wildcard_3 is changed
- wildcard_4 is not changed

View file

@ -1,118 +0,0 @@
---
- name: Registering container name
set_fact:
cname: "{{ cname_prefix ~ '-hi' }}"
- name: Registering container name
set_fact:
cnames: "{{ cnames + [cname] }}"
####################################################################
## container_default_behavior: compatibility #######################
####################################################################
- name: Start container (check)
docker_container:
name: "{{ cname }}"
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
state: started
container_default_behavior: compatibility
check_mode: yes
register: start_1
- name: Start container
docker_container:
name: "{{ cname }}"
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
state: started
container_default_behavior: compatibility
register: start_2
- name: Start container (idempotent)
docker_container:
name: "{{ cname }}"
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
state: started
container_default_behavior: compatibility
register: start_3
- name: Start container (idempotent check)
docker_container:
name: "{{ cname }}"
image: alpine:3.8
state: started
container_default_behavior: compatibility
check_mode: yes
register: start_4
- name: cleanup
docker_container:
name: "{{ cname }}"
state: absent
force_kill: yes
diff: no
- assert:
that:
- start_1 is changed
- start_2 is changed
- start_3 is not changed
- start_4 is not changed
####################################################################
## container_default_behavior: no_defaults #########################
####################################################################
- name: Start container (check)
docker_container:
name: "{{ cname }}"
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
state: started
container_default_behavior: no_defaults
check_mode: yes
register: start_1
- name: Start container
docker_container:
name: "{{ cname }}"
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
state: started
container_default_behavior: no_defaults
register: start_2
- name: Start container (idempotent)
docker_container:
name: "{{ cname }}"
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
state: started
container_default_behavior: no_defaults
register: start_3
- name: Start container (idempotent check)
docker_container:
name: "{{ cname }}"
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
state: started
container_default_behavior: no_defaults
check_mode: yes
register: start_4
- name: cleanup
docker_container:
name: "{{ cname }}"
state: absent
force_kill: yes
diff: no
- assert:
that:
- start_1 is changed
- start_2 is changed
- start_3 is not changed
- start_4 is not changed

View file

@ -1,146 +0,0 @@
---
- name: Registering container name
set_fact:
cname: "{{ cname_prefix ~ '-iid' }}"
- name: Registering container name
set_fact:
cnames: "{{ cnames + [cname] }}"
- name: Pull images
docker_image:
name: "{{ image }}"
source: pull
loop:
- "hello-world:latest"
- "alpine:3.8"
loop_control:
loop_var: image
- name: Get image ID of hello-world and alpine images
docker_image_info:
name:
- "hello-world:latest"
- "alpine:3.8"
register: image_info
- assert:
that:
- image_info.images | length == 2
- name: Print image IDs
debug:
msg: "hello-world: {{ image_info.images[0].Id }}; alpine: {{ image_info.images[1].Id }}"
- name: Create container with hello-world image via ID
docker_container:
image: "{{ image_info.images[0].Id }}"
name: "{{ cname }}"
state: present
force_kill: yes
register: create_1
- name: Create container with hello-world image via ID (idempotent)
docker_container:
image: "{{ image_info.images[0].Id }}"
name: "{{ cname }}"
state: present
force_kill: yes
register: create_2
- name: Create container with alpine image via ID
docker_container:
image: "{{ image_info.images[1].Id }}"
name: "{{ cname }}"
state: present
force_kill: yes
register: create_3
- name: Create container with alpine image via ID (idempotent)
docker_container:
image: "{{ image_info.images[1].Id }}"
name: "{{ cname }}"
state: present
force_kill: yes
register: create_4
- name: Untag image
# Image will not be deleted since the container still uses it
docker_image:
name: alpine:3.8
force_absent: yes
state: absent
- name: Create container with alpine image via name (check mode, will pull, same image)
docker_container:
image: alpine:3.8
name: "{{ cname }}"
state: present
register: create_5
check_mode: yes
- name: Create container with alpine image via name (will pull, same image)
docker_container:
image: alpine:3.8
name: "{{ cname }}"
state: present
register: create_6
- name: Cleanup
docker_container:
name: "{{ cname }}"
state: absent
force_kill: yes
diff: no
- assert:
that:
- create_1 is changed
- create_2 is not changed
- create_3 is changed
- create_4 is not changed
- create_5 is changed
- create_6 is changed
- create_6.container.Image == image_info.images[1].Id
- create_6.container.Id == create_4.container.Id # make sure container wasn't recreated
- name: set Digests
set_fact:
digest_hello_world_2016: 0256e8a36e2070f7bf2d0b0763dbabdd67798512411de4cdcf9431a1feb60fd9
digest_hello_world_2019: 2557e3c07ed1e38f26e389462d03ed943586f744621577a99efb77324b0fe535
- name: Create container with hello-world image via old digest
docker_container:
image: "hello-world@sha256:{{ digest_hello_world_2016 }}"
name: "{{ cname }}"
state: present
force_kill: yes
register: digest_1
- name: Create container with hello-world image via old digest (idempotent)
docker_container:
image: "hello-world@sha256:{{ digest_hello_world_2016 }}"
name: "{{ cname }}"
state: present
force_kill: yes
register: digest_2
- name: Update container with hello-world image via new digest
docker_container:
image: "hello-world@sha256:{{ digest_hello_world_2019 }}"
name: "{{ cname }}"
state: present
force_kill: yes
register: digest_3
- name: Cleanup
docker_container:
name: "{{ cname }}"
state: absent
force_kill: yes
diff: no
- assert:
that:
- digest_1 is changed
- digest_2 is not changed
- digest_3 is changed

View file

@ -1,445 +0,0 @@
---
- name: Registering container name
set_fact:
cname: "{{ cname_prefix ~ '-mounts' }}"
cname_h1: "{{ cname_prefix ~ '-mounts-h1' }}"
cname_h2: "{{ cname_prefix ~ '-mounts-h2' }}"
- name: Registering container name
set_fact:
cnames: "{{ cnames + [cname, cname_h1, cname_h2] }}"
####################################################################
## keep_volumes ####################################################
####################################################################
# TODO: - keep_volumes
####################################################################
## mounts ##########################################################
####################################################################
- name: mounts
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
mounts:
- source: /tmp
target: /tmp
type: bind
- source: /
target: /whatever
type: bind
read_only: no
register: mounts_1
ignore_errors: yes
- name: mounts (idempotency)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
mounts:
- source: /
target: /whatever
type: bind
read_only: no
- source: /tmp
target: /tmp
type: bind
register: mounts_2
ignore_errors: yes
- name: mounts (less mounts)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
mounts:
- source: /tmp
target: /tmp
type: bind
register: mounts_3
ignore_errors: yes
- name: mounts (more mounts)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
mounts:
- source: /tmp
target: /tmp
type: bind
- source: /tmp
target: /somewhereelse
type: bind
read_only: yes
force_kill: yes
register: mounts_4
ignore_errors: yes
- name: mounts (different modes)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
mounts:
- source: /tmp
target: /tmp
type: bind
- source: /tmp
target: /somewhereelse
type: bind
read_only: no
force_kill: yes
register: mounts_5
ignore_errors: yes
- name: mounts (endpoint collision)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
mounts:
- source: /home
target: /x
type: bind
- source: /etc
target: /x
type: bind
read_only: no
force_kill: yes
register: mounts_6
ignore_errors: yes
- name: cleanup
docker_container:
name: "{{ cname }}"
state: absent
force_kill: yes
diff: no
- assert:
that:
- mounts_1 is changed
- mounts_2 is not changed
- mounts_3 is not changed
- mounts_4 is changed
- mounts_5 is changed
- mounts_6 is failed
- "'The mount point \"/x\" appears twice in the mounts option' == mounts_6.msg"
when: docker_py_version is version('2.6.0', '>=')
- assert:
that:
- mounts_1 is failed
- "('version is ' ~ docker_py_version ~ ' ') in mounts_1.msg"
- "'Minimum version required is 2.6.0 ' in mounts_1.msg"
when: docker_py_version is version('2.6.0', '<')
####################################################################
## mounts + volumes ################################################
####################################################################
- name: mounts + volumes
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
mounts:
- source: /
target: /whatever
type: bind
read_only: yes
volumes:
- /tmp:/tmp
register: mounts_volumes_1
ignore_errors: yes
- name: mounts + volumes (idempotency)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
mounts:
- source: /
target: /whatever
type: bind
read_only: yes
volumes:
- /tmp:/tmp
register: mounts_volumes_2
ignore_errors: yes
- name: mounts + volumes (switching)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
mounts:
- source: /tmp
target: /tmp
type: bind
read_only: no
volumes:
- /:/whatever:ro
force_kill: yes
register: mounts_volumes_3
ignore_errors: yes
- name: mounts + volumes (collision, should fail)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
mounts:
- source: /tmp
target: /tmp
type: bind
read_only: no
volumes:
- /tmp:/tmp
force_kill: yes
register: mounts_volumes_4
ignore_errors: yes
- name: cleanup
docker_container:
name: "{{ cname }}"
state: absent
force_kill: yes
diff: no
- assert:
that:
- mounts_volumes_1 is changed
- mounts_volumes_2 is not changed
- mounts_volumes_3 is changed
- mounts_volumes_4 is failed
- "'The mount point \"/tmp\" appears both in the volumes and mounts option' in mounts_volumes_4.msg"
when: docker_py_version is version('2.6.0', '>=')
- assert:
that:
- mounts_volumes_1 is failed
- "('version is ' ~ docker_py_version ~ ' ') in mounts_1.msg"
- "'Minimum version required is 2.6.0 ' in mounts_1.msg"
when: docker_py_version is version('2.6.0', '<')
####################################################################
## volume_driver ###################################################
####################################################################
- name: volume_driver
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
volume_driver: local
state: started
register: volume_driver_1
- name: volume_driver (idempotency)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
volume_driver: local
state: started
register: volume_driver_2
- name: volume_driver (change)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
volume_driver: /
state: started
force_kill: yes
register: volume_driver_3
- name: cleanup
docker_container:
name: "{{ cname }}"
state: absent
force_kill: yes
diff: no
- assert:
that:
- volume_driver_1 is changed
- volume_driver_2 is not changed
- volume_driver_3 is changed
####################################################################
## volumes #########################################################
####################################################################
- name: volumes
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
volumes:
- "/tmp:/tmp"
- "/:/whatever:rw,z"
- "/anon:rw"
register: volumes_1
- name: volumes (idempotency)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
volumes:
- "/:/whatever:rw,z"
- "/tmp:/tmp"
- "/anon:rw"
register: volumes_2
- name: volumes (less volumes)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
volumes:
- "/tmp:/tmp"
register: volumes_3
- name: volumes (more volumes)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
volumes:
- "/tmp:/tmp"
- "/tmp:/somewhereelse:ro,Z"
force_kill: yes
register: volumes_4
- name: volumes (different modes)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
volumes:
- "/tmp:/tmp"
- "/tmp:/somewhereelse:ro"
force_kill: yes
register: volumes_5
- name: volumes (collision)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
volumes:
- "/etc:/tmp"
- "/home:/tmp:ro"
force_kill: yes
register: volumes_6
ignore_errors: yes
- name: cleanup
docker_container:
name: "{{ cname }}"
state: absent
force_kill: yes
diff: no
- assert:
that:
- volumes_1 is changed
- volumes_1.container.Config.Volumes | length == 1
- volumes_1.container.Config.Volumes['/anon:rw'] | length == 0
- volumes_2 is not changed
- volumes_3 is not changed
- volumes_4 is changed
- not volumes_4.container.Config.Volumes
- volumes_5 is changed
- volumes_6 is failed
- "'The mount point \"/tmp\" appears twice in the volumes option' in volumes_6.msg"
####################################################################
## volumes_from ####################################################
####################################################################
- name: start helpers
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ container_name }}"
state: started
volumes:
- "{{ '/tmp:/tmp' if container_name == cname_h1 else '/:/whatever:ro' }}"
loop:
- "{{ cname_h1 }}"
- "{{ cname_h2 }}"
loop_control:
loop_var: container_name
- name: volumes_from
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
volumes_from: "{{ cname_h1 }}"
register: volumes_from_1
- name: volumes_from (idempotency)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
volumes_from: "{{ cname_h1 }}"
register: volumes_from_2
- name: volumes_from (change)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
volumes_from: "{{ cname_h2 }}"
force_kill: yes
register: volumes_from_3
- name: cleanup
docker_container:
name: "{{ container_name }}"
state: absent
force_kill: yes
loop:
- "{{ cname }}"
- "{{ cname_h1 }}"
- "{{ cname_h2 }}"
loop_control:
loop_var: container_name
diff: no
- assert:
that:
- volumes_from_1 is changed
- volumes_from_2 is not changed
- volumes_from_3 is changed
####################################################################
####################################################################
####################################################################

View file

@ -1,747 +0,0 @@
---
- name: Registering container name
set_fact:
cname: "{{ cname_prefix ~ '-network' }}"
cname_h1: "{{ cname_prefix ~ '-network-h1' }}"
nname_1: "{{ cname_prefix ~ '-network-1' }}"
nname_2: "{{ cname_prefix ~ '-network-2' }}"
nname_3: "{{ cname_prefix ~ '-network-3' }}"
- name: Registering container name
set_fact:
cnames: "{{ cnames + [cname, cname_h1] }}"
dnetworks: "{{ dnetworks + [nname_1, nname_2, nname_3] }}"
- name: Create networks
docker_network:
name: "{{ network_name }}"
state: present
loop:
- "{{ nname_1 }}"
- "{{ nname_2 }}"
loop_control:
loop_var: network_name
when: docker_py_version is version('1.10.0', '>=')
- set_fact:
subnet_ipv4_base: 192.168.{{ 64 + (192 | random) }}
subnet_ipv6_base: fdb6:feea:{{ '%0.4x:%0.4x' | format(65536 | random, 65536 | random) }}
# If netaddr would be installed on the controller, one could do:
# subnet_ipv4: "192.168.{{ 64 + (192 | random) }}.0/24"
# subnet_ipv6: "fdb6:feea:{{ '%0.4x:%0.4x' | format(65536 | random, 65536 | random) }}::/64"
- set_fact:
subnet_ipv4: "{{ subnet_ipv4_base }}.0/24"
subnet_ipv6: "{{ subnet_ipv6_base }}::/64"
nname_3_ipv4_2: "{{ subnet_ipv4_base }}.2"
nname_3_ipv4_3: "{{ subnet_ipv4_base }}.3"
nname_3_ipv4_4: "{{ subnet_ipv4_base }}.4"
nname_3_ipv6_2: "{{ subnet_ipv6_base }}::2"
nname_3_ipv6_3: "{{ subnet_ipv6_base }}::3"
nname_3_ipv6_4: "{{ subnet_ipv6_base }}::4"
# If netaddr would be installed on the controller, one could do:
# nname_3_ipv4_2: "{{ subnet_ipv4 | ansible.netcommon.next_nth_usable(2) }}"
# nname_3_ipv4_3: "{{ subnet_ipv4 | ansible.netcommon.next_nth_usable(3) }}"
# nname_3_ipv4_4: "{{ subnet_ipv4 | ansible.netcommon.next_nth_usable(4) }}"
# nname_3_ipv6_2: "{{ subnet_ipv6 | ansible.netcommon.next_nth_usable(2) }}"
# nname_3_ipv6_3: "{{ subnet_ipv6 | ansible.netcommon.next_nth_usable(3) }}"
# nname_3_ipv6_4: "{{ subnet_ipv6 | ansible.netcommon.next_nth_usable(4) }}"
- debug:
msg: "Chose random IPv4 subnet {{ subnet_ipv4 }} and random IPv6 subnet {{ subnet_ipv6 }}"
- name: Create network with fixed IPv4 and IPv6 subnets
docker_network:
name: "{{ nname_3 }}"
enable_ipv6: yes
ipam_config:
- subnet: "{{ subnet_ipv4 }}"
- subnet: "{{ subnet_ipv6 }}"
state: present
when: docker_py_version is version('1.10.0', '>=')
####################################################################
## network_mode ####################################################
####################################################################
- name: network_mode
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
network_mode: host
register: network_mode_1
- name: network_mode (idempotency)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
network_mode: host
register: network_mode_2
- name: network_mode (change)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
network_mode: none
force_kill: yes
register: network_mode_3
- name: network_mode (container mode setup)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname_h1 }}"
state: started
register: cname_h1_id
- name: network_mode (container mode)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
network_mode: "container:{{ cname_h1_id.container.Id }}"
force_kill: yes
register: network_mode_4
- name: network_mode (container mode idempotency)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
network_mode: "container:{{ cname_h1 }}"
register: network_mode_5
- name: cleanup
docker_container:
name: "{{ container_name }}"
state: absent
force_kill: yes
loop:
- "{{ cname }}"
- "{{ cname_h1 }}"
loop_control:
loop_var: container_name
diff: no
- assert:
that:
- network_mode_1 is changed
- network_mode_1.container.HostConfig.NetworkMode == 'host'
- network_mode_2 is not changed
- network_mode_2.container.HostConfig.NetworkMode == 'host'
- network_mode_3 is changed
- network_mode_3.container.HostConfig.NetworkMode == 'none'
- network_mode_4 is changed
- network_mode_4.container.HostConfig.NetworkMode == 'container:' ~ cname_h1_id.container.Id
- network_mode_5 is not changed
- network_mode_5.container.HostConfig.NetworkMode == 'container:' ~ cname_h1_id.container.Id
####################################################################
## networks, purge_networks for networks_cli_compatible=no #########
####################################################################
- block:
- name: networks_cli_compatible=no, networks w/o purge_networks
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
networks:
- name: "{{ nname_1 }}"
- name: "{{ nname_2 }}"
networks_cli_compatible: no
register: networks_1
- name: networks_cli_compatible=no, networks w/o purge_networks
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
networks:
- name: "{{ nname_1 }}"
- name: "{{ nname_2 }}"
networks_cli_compatible: no
register: networks_2
- name: networks_cli_compatible=no, networks, purge_networks
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
purge_networks: yes
networks:
- name: bridge
- name: "{{ nname_1 }}"
networks_cli_compatible: no
force_kill: yes
register: networks_3
- name: networks_cli_compatible=no, networks, purge_networks (idempotency)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
purge_networks: yes
networks:
- name: "{{ nname_1 }}"
- name: bridge
networks_cli_compatible: no
register: networks_4
- name: networks_cli_compatible=no, networks (less networks)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
networks:
- name: bridge
networks_cli_compatible: no
register: networks_5
- name: networks_cli_compatible=no, networks, purge_networks (less networks)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
purge_networks: yes
networks:
- name: bridge
networks_cli_compatible: no
force_kill: yes
register: networks_6
- name: networks_cli_compatible=no, networks, purge_networks (more networks)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
purge_networks: yes
networks:
- name: bridge
- name: "{{ nname_2 }}"
networks_cli_compatible: no
force_kill: yes
register: networks_7
- name: cleanup
docker_container:
name: "{{ cname }}"
state: absent
force_kill: yes
diff: no
- assert:
that:
# networks_1 has networks default, 'bridge', nname_1
- networks_1 is changed
- networks_1.container.NetworkSettings.Networks | length == 3
- nname_1 in networks_1.container.NetworkSettings.Networks
- nname_2 in networks_1.container.NetworkSettings.Networks
- "'default' in networks_1.container.NetworkSettings.Networks or 'bridge' in networks_1.container.NetworkSettings.Networks"
# networks_2 has networks default, 'bridge', nname_1
- networks_2 is not changed
- networks_2.container.NetworkSettings.Networks | length == 3
- nname_1 in networks_2.container.NetworkSettings.Networks
- nname_2 in networks_1.container.NetworkSettings.Networks
- "'default' in networks_1.container.NetworkSettings.Networks or 'bridge' in networks_1.container.NetworkSettings.Networks"
# networks_3 has networks 'bridge', nname_1
- networks_3 is changed
- networks_3.container.NetworkSettings.Networks | length == 2
- nname_1 in networks_3.container.NetworkSettings.Networks
- "'default' in networks_3.container.NetworkSettings.Networks or 'bridge' in networks_3.container.NetworkSettings.Networks"
# networks_4 has networks 'bridge', nname_1
- networks_4 is not changed
- networks_4.container.NetworkSettings.Networks | length == 2
- nname_1 in networks_4.container.NetworkSettings.Networks
- "'default' in networks_4.container.NetworkSettings.Networks or 'bridge' in networks_4.container.NetworkSettings.Networks"
# networks_5 has networks 'bridge', nname_1
- networks_5 is not changed
- networks_5.container.NetworkSettings.Networks | length == 2
- nname_1 in networks_5.container.NetworkSettings.Networks
- "'default' in networks_5.container.NetworkSettings.Networks or 'bridge' in networks_5.container.NetworkSettings.Networks"
# networks_6 has networks 'bridge'
- networks_6 is changed
- networks_6.container.NetworkSettings.Networks | length == 1
- "'default' in networks_6.container.NetworkSettings.Networks or 'bridge' in networks_6.container.NetworkSettings.Networks"
# networks_7 has networks 'bridge', nname_2
- networks_7 is changed
- networks_7.container.NetworkSettings.Networks | length == 2
- nname_2 in networks_7.container.NetworkSettings.Networks
- "'default' in networks_7.container.NetworkSettings.Networks or 'bridge' in networks_7.container.NetworkSettings.Networks"
when: docker_py_version is version('1.10.0', '>=')
####################################################################
## networks for networks_cli_compatible=yes ########################
####################################################################
- block:
- name: networks_cli_compatible=yes, networks specified
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
networks:
- name: "{{ nname_1 }}"
aliases:
- alias1
- alias2
- name: "{{ nname_2 }}"
networks_cli_compatible: yes
register: networks_1
- name: networks_cli_compatible=yes, networks specified
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
networks:
- name: "{{ nname_1 }}"
- name: "{{ nname_2 }}"
networks_cli_compatible: yes
register: networks_2
- name: cleanup
docker_container:
name: "{{ cname }}"
state: absent
force_kill: yes
diff: no
- name: networks_cli_compatible=yes, empty networks list specified
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
networks: []
networks_cli_compatible: yes
register: networks_3
- name: networks_cli_compatible=yes, empty networks list specified
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
networks: []
networks_cli_compatible: yes
register: networks_4
- name: networks_cli_compatible=yes, empty networks list specified, purge_networks
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
networks: []
networks_cli_compatible: yes
purge_networks: yes
force_kill: yes
register: networks_5
- name: cleanup
docker_container:
name: "{{ cname }}"
state: absent
force_kill: yes
diff: no
- name: networks_cli_compatible=yes, networks not specified
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
networks_cli_compatible: yes
force_kill: yes
register: networks_6
- name: networks_cli_compatible=yes, networks not specified
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
networks_cli_compatible: yes
register: networks_7
- name: networks_cli_compatible=yes, networks not specified, purge_networks
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
networks_cli_compatible: yes
purge_networks: yes
force_kill: yes
register: networks_8
- name: cleanup
docker_container:
name: "{{ cname }}"
state: absent
force_kill: yes
diff: no
- debug: var=networks_3
- assert:
that:
# networks_1 has networks nname_1, nname_2
- networks_1 is changed
- networks_1.container.NetworkSettings.Networks | length == 2
- nname_1 in networks_1.container.NetworkSettings.Networks
- nname_2 in networks_1.container.NetworkSettings.Networks
# networks_2 has networks nname_1, nname_2
- networks_2 is not changed
- networks_2.container.NetworkSettings.Networks | length == 2
- nname_1 in networks_2.container.NetworkSettings.Networks
- nname_2 in networks_1.container.NetworkSettings.Networks
# networks_3 has networks 'bridge'
- networks_3 is changed
- networks_3.container.NetworkSettings.Networks | length == 1
- "'default' in networks_3.container.NetworkSettings.Networks or 'bridge' in networks_3.container.NetworkSettings.Networks"
# networks_4 has networks 'bridge'
- networks_4 is not changed
- networks_4.container.NetworkSettings.Networks | length == 1
- "'default' in networks_4.container.NetworkSettings.Networks or 'bridge' in networks_4.container.NetworkSettings.Networks"
# networks_5 has no networks
- networks_5 is changed
- networks_5.container.NetworkSettings.Networks | length == 0
# networks_6 has networks 'bridge'
- networks_6 is changed
- networks_6.container.NetworkSettings.Networks | length == 1
- "'default' in networks_6.container.NetworkSettings.Networks or 'bridge' in networks_6.container.NetworkSettings.Networks"
# networks_7 has networks 'bridge'
- networks_7 is not changed
- networks_7.container.NetworkSettings.Networks | length == 1
- "'default' in networks_7.container.NetworkSettings.Networks or 'bridge' in networks_7.container.NetworkSettings.Networks"
# networks_8 has no networks
- networks_8 is changed
- networks_8.container.NetworkSettings.Networks | length == 0
when: docker_py_version is version('1.10.0', '>=')
####################################################################
## networks with comparisons #######################################
####################################################################
- block:
- name: create container with one network
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
networks:
- name: "{{ nname_1 }}"
networks_cli_compatible: yes
register: networks_1
- name: different networks, comparisons=ignore
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
networks:
- name: "{{ nname_2 }}"
networks_cli_compatible: yes
comparisons:
networks: ignore
register: networks_2
- name: less networks, comparisons=ignore
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
networks: []
networks_cli_compatible: yes
comparisons:
networks: ignore
register: networks_3
- name: less networks, comparisons=allow_more_present
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
networks: []
networks_cli_compatible: yes
comparisons:
networks: allow_more_present
register: networks_4
- name: different networks, comparisons=allow_more_present
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
networks:
- name: "{{ nname_2 }}"
networks_cli_compatible: yes
comparisons:
networks: allow_more_present
force_kill: yes
register: networks_5
- name: different networks, comparisons=strict
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
networks:
- name: "{{ nname_2 }}"
networks_cli_compatible: yes
comparisons:
networks: strict
force_kill: yes
register: networks_6
- name: less networks, comparisons=strict
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
networks: []
networks_cli_compatible: yes
comparisons:
networks: strict
force_kill: yes
register: networks_7
- name: cleanup
docker_container:
name: "{{ cname }}"
state: absent
force_kill: yes
diff: no
- assert:
that:
# networks_1 has networks nname_1
- networks_1 is changed
- networks_1.container.NetworkSettings.Networks | length == 1
- nname_1 in networks_1.container.NetworkSettings.Networks
# networks_2 has networks nname_1
- networks_2 is not changed
- networks_2.container.NetworkSettings.Networks | length == 1
- nname_1 in networks_2.container.NetworkSettings.Networks
# networks_3 has networks nname_1
- networks_3 is not changed
- networks_3.container.NetworkSettings.Networks | length == 1
- nname_1 in networks_3.container.NetworkSettings.Networks
# networks_4 has networks nname_1
- networks_4 is not changed
- networks_4.container.NetworkSettings.Networks | length == 1
- nname_1 in networks_4.container.NetworkSettings.Networks
# networks_5 has networks nname_1, nname_2
- networks_5 is changed
- networks_5.container.NetworkSettings.Networks | length == 2
- nname_1 in networks_5.container.NetworkSettings.Networks
- nname_2 in networks_5.container.NetworkSettings.Networks
# networks_6 has networks nname_2
- networks_6 is changed
- networks_6.container.NetworkSettings.Networks | length == 1
- nname_2 in networks_6.container.NetworkSettings.Networks
# networks_7 has no networks
- networks_7 is changed
- networks_7.container.NetworkSettings.Networks | length == 0
when: docker_py_version is version('1.10.0', '>=')
####################################################################
## networks with IP address ########################################
####################################################################
- block:
- name: create container (stopped) with one network and fixed IP
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: stopped
networks:
- name: "{{ nname_3 }}"
ipv4_address: "{{ nname_3_ipv4_2 }}"
ipv6_address: "{{ nname_3_ipv6_2 }}"
networks_cli_compatible: yes
register: networks_1
- name: create container (stopped) with one network and fixed IP (idempotent)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: stopped
networks:
- name: "{{ nname_3 }}"
ipv4_address: "{{ nname_3_ipv4_2 }}"
ipv6_address: "{{ nname_3_ipv6_2 }}"
networks_cli_compatible: yes
register: networks_2
- name: create container (stopped) with one network and fixed IP (different IPv4)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: stopped
networks:
- name: "{{ nname_3 }}"
ipv4_address: "{{ nname_3_ipv4_3 }}"
ipv6_address: "{{ nname_3_ipv6_2 }}"
networks_cli_compatible: yes
register: networks_3
- name: create container (stopped) with one network and fixed IP (different IPv6)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: stopped
networks:
- name: "{{ nname_3 }}"
ipv4_address: "{{ nname_3_ipv4_3 }}"
ipv6_address: "{{ nname_3_ipv6_3 }}"
networks_cli_compatible: yes
register: networks_4
- name: create container (started) with one network and fixed IP
docker_container:
name: "{{ cname }}"
state: started
register: networks_5
- name: create container (started) with one network and fixed IP (different IPv4)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
networks:
- name: "{{ nname_3 }}"
ipv4_address: "{{ nname_3_ipv4_4 }}"
ipv6_address: "{{ nname_3_ipv6_3 }}"
networks_cli_compatible: yes
force_kill: yes
register: networks_6
- name: create container (started) with one network and fixed IP (different IPv6)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
networks:
- name: "{{ nname_3 }}"
ipv4_address: "{{ nname_3_ipv4_4 }}"
ipv6_address: "{{ nname_3_ipv6_4 }}"
networks_cli_compatible: yes
force_kill: yes
register: networks_7
- name: create container (started) with one network and fixed IP (idempotent)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
networks:
- name: "{{ nname_3 }}"
ipv4_address: "{{ nname_3_ipv4_4 }}"
ipv6_address: "{{ nname_3_ipv6_4 }}"
networks_cli_compatible: yes
register: networks_8
- name: cleanup
docker_container:
name: "{{ cname }}"
state: absent
force_kill: yes
diff: no
- assert:
that:
- networks_1 is changed
- networks_1.container.NetworkSettings.Networks[nname_3].IPAMConfig.IPv4Address == nname_3_ipv4_2
- networks_1.container.NetworkSettings.Networks[nname_3].IPAMConfig.IPv6Address | normalize_ipaddr == nname_3_ipv6_2 | normalize_ipaddr
- networks_1.container.NetworkSettings.Networks[nname_3].IPAddress == ""
- networks_1.container.NetworkSettings.Networks[nname_3].GlobalIPv6Address == ""
- networks_2 is not changed
- networks_2.container.NetworkSettings.Networks[nname_3].IPAMConfig.IPv4Address == nname_3_ipv4_2
- networks_2.container.NetworkSettings.Networks[nname_3].IPAMConfig.IPv6Address | normalize_ipaddr == nname_3_ipv6_2 | normalize_ipaddr
- networks_2.container.NetworkSettings.Networks[nname_3].IPAddress == ""
- networks_2.container.NetworkSettings.Networks[nname_3].GlobalIPv6Address == ""
- networks_3 is changed
- networks_3.container.NetworkSettings.Networks[nname_3].IPAMConfig.IPv4Address == nname_3_ipv4_3
- networks_3.container.NetworkSettings.Networks[nname_3].IPAMConfig.IPv6Address | normalize_ipaddr == nname_3_ipv6_2 | normalize_ipaddr
- networks_3.container.NetworkSettings.Networks[nname_3].IPAddress == ""
- networks_3.container.NetworkSettings.Networks[nname_3].GlobalIPv6Address == ""
- networks_4 is changed
- networks_4.container.NetworkSettings.Networks[nname_3].IPAMConfig.IPv4Address == nname_3_ipv4_3
- networks_4.container.NetworkSettings.Networks[nname_3].IPAMConfig.IPv6Address | normalize_ipaddr == nname_3_ipv6_3 | normalize_ipaddr
- networks_4.container.NetworkSettings.Networks[nname_3].IPAddress == ""
- networks_4.container.NetworkSettings.Networks[nname_3].GlobalIPv6Address == ""
- networks_5 is changed
- networks_5.container.NetworkSettings.Networks[nname_3].IPAMConfig.IPv4Address == nname_3_ipv4_3
- networks_5.container.NetworkSettings.Networks[nname_3].IPAMConfig.IPv6Address | normalize_ipaddr == nname_3_ipv6_3 | normalize_ipaddr
- networks_5.container.NetworkSettings.Networks[nname_3].IPAddress == nname_3_ipv4_3
- networks_5.container.NetworkSettings.Networks[nname_3].GlobalIPv6Address | normalize_ipaddr == nname_3_ipv6_3 | normalize_ipaddr
- networks_6 is changed
- networks_6.container.NetworkSettings.Networks[nname_3].IPAMConfig.IPv4Address == nname_3_ipv4_4
- networks_6.container.NetworkSettings.Networks[nname_3].IPAMConfig.IPv6Address | normalize_ipaddr == nname_3_ipv6_3 | normalize_ipaddr
- networks_6.container.NetworkSettings.Networks[nname_3].IPAddress == nname_3_ipv4_4
- networks_6.container.NetworkSettings.Networks[nname_3].GlobalIPv6Address | normalize_ipaddr == nname_3_ipv6_3 | normalize_ipaddr
- networks_7 is changed
- networks_7.container.NetworkSettings.Networks[nname_3].IPAMConfig.IPv4Address == nname_3_ipv4_4
- networks_7.container.NetworkSettings.Networks[nname_3].IPAMConfig.IPv6Address | normalize_ipaddr == nname_3_ipv6_4 | normalize_ipaddr
- networks_7.container.NetworkSettings.Networks[nname_3].IPAddress == nname_3_ipv4_4
- networks_7.container.NetworkSettings.Networks[nname_3].GlobalIPv6Address | normalize_ipaddr == nname_3_ipv6_4 | normalize_ipaddr
- networks_8 is not changed
- networks_8.container.NetworkSettings.Networks[nname_3].IPAMConfig.IPv4Address == nname_3_ipv4_4
- networks_8.container.NetworkSettings.Networks[nname_3].IPAMConfig.IPv6Address | normalize_ipaddr == nname_3_ipv6_4 | normalize_ipaddr
- networks_8.container.NetworkSettings.Networks[nname_3].IPAddress == nname_3_ipv4_4
- networks_8.container.NetworkSettings.Networks[nname_3].GlobalIPv6Address | normalize_ipaddr == nname_3_ipv6_4 | normalize_ipaddr
when: docker_py_version is version('1.10.0', '>=')
####################################################################
####################################################################
####################################################################
- name: Delete networks
docker_network:
name: "{{ network_name }}"
state: absent
force: yes
loop:
- "{{ nname_1 }}"
- "{{ nname_2 }}"
- "{{ nname_3 }}"
loop_control:
loop_var: network_name
when: docker_py_version is version('1.10.0', '>=')

View file

@ -1,286 +0,0 @@
---
- name: Registering container name
set_fact:
cname: "{{ cname_prefix ~ '-options' }}"
cname2: "{{ cname_prefix ~ '-options-h1' }}"
- name: Registering container name
set_fact:
cnames: "{{ cnames + [cname, cname2] }}"
####################################################################
## published_ports: all ############################################
####################################################################
- name: published_ports -- all
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
exposed_ports:
- "9001"
- "9002"
published_ports:
- all
force_kill: yes
register: published_ports_1
- name: published_ports -- all (idempotency)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
exposed_ports:
- "9001"
- "9002"
published_ports:
- all
force_kill: yes
register: published_ports_2
- name: published_ports -- all (writing out 'all')
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
exposed_ports:
- "9001"
- "9002"
published_ports:
- "9001"
- "9002"
force_kill: yes
register: published_ports_3
- name: published_ports -- all (idempotency 2)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
exposed_ports:
- "9001"
- "9002"
published_ports:
- "9002"
- "9001"
force_kill: yes
register: published_ports_4
- name: published_ports -- all (switching back to 'all')
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
exposed_ports:
- "9001"
- "9002"
published_ports:
- all
force_kill: yes
register: published_ports_5
- name: cleanup
docker_container:
name: "{{ cname }}"
state: absent
force_kill: yes
diff: no
- assert:
that:
- published_ports_1 is changed
- published_ports_2 is not changed
- published_ports_3 is changed
- published_ports_4 is not changed
- published_ports_5 is changed
####################################################################
## published_ports: port range #####################################
####################################################################
- name: published_ports -- port range
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
exposed_ports:
- "9001"
- "9010-9050"
published_ports:
- "9001:9001"
- "9010-9050:9010-9050"
force_kill: yes
register: published_ports_1
- name: published_ports -- port range (idempotency)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
exposed_ports:
- "9001"
- "9010-9050"
published_ports:
- "9001:9001"
- "9010-9050:9010-9050"
force_kill: yes
register: published_ports_2
- name: published_ports -- port range (different range)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
exposed_ports:
- "9001"
- "9010-9050"
published_ports:
- "9001:9001"
- "9020-9060:9020-9060"
force_kill: yes
register: published_ports_3
- name: cleanup
docker_container:
name: "{{ cname }}"
state: absent
force_kill: yes
diff: no
- assert:
that:
- published_ports_1 is changed
- published_ports_2 is not changed
- published_ports_3 is changed
####################################################################
## published_ports: one-element container port range ###############
####################################################################
- name: published_ports -- one-element container port range
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ item }}"
state: started
published_ports:
- "9010-9050:9010"
force_kill: yes
loop:
- '{{ cname }}'
- '{{ cname2 }}'
register: published_ports_1
- name: published_ports -- one-element container port range (idempotency)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ item }}"
state: started
published_ports:
- "9010-9050:9010"
force_kill: yes
loop:
- '{{ cname }}'
- '{{ cname2 }}'
register: published_ports_2
- name: published_ports -- one-element container port range (different range)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ item }}"
state: started
published_ports:
- "9010-9051:9010"
force_kill: yes
loop:
- '{{ cname }}'
- '{{ cname2 }}'
register: published_ports_3
- name: cleanup
docker_container:
name: "{{ item }}"
state: absent
force_kill: yes
loop:
- '{{ cname }}'
- '{{ cname2 }}'
diff: no
- assert:
that:
- published_ports_1 is changed
- published_ports_2 is not changed
- published_ports_3 is changed
####################################################################
## published_ports: IPv6 addresses #################################
####################################################################
- name: published_ports -- IPv6
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
published_ports:
- "[::1]:9001:9001"
force_kill: yes
register: published_ports_1
- name: published_ports -- IPv6 (idempotency)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
published_ports:
- "[::1]:9001:9001"
force_kill: yes
register: published_ports_2
- name: published_ports -- IPv6 (different IP)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
published_ports:
- "127.0.0.1:9001:9001"
force_kill: yes
register: published_ports_3
- name: published_ports -- IPv6 (hostname)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
published_ports:
- "localhost:9001:9001"
force_kill: yes
register: published_ports_4
ignore_errors: yes
- name: cleanup
docker_container:
name: "{{ cname }}"
state: absent
force_kill: yes
diff: no
- assert:
that:
- published_ports_1 is changed
- published_ports_2 is not changed
- published_ports_3 is changed
- published_ports_4 is failed

View file

@ -1,34 +0,0 @@
---
# Regression test for https://github.com/ansible/ansible/pull/45700
- name: Registering container name
set_fact:
cname: "{{ cname_prefix ~ '-45700' }}"
- name: Registering container name
set_fact:
cnames: "{{ cnames + [cname] }}"
- name: Start container
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
- name: Stop container with a lot of invalid options
docker_container:
name: "{{ cname }}"
force_kill: yes
# Some options with "invalid" values, which would
# have to be parsed. The values are "invalid" because
# the containers and networks listed here do not exist.
# This can happen because the networks are removed
# before the container is stopped (see
# https://github.com/ansible/ansible/issues/45486).
networks:
- name: "nonexistant-network-{{ (2**32) | random }}"
published_ports:
- '1:2'
- '3'
links:
- "nonexistant-container-{{ (2**32) | random }}:test"
state: absent

View file

@ -1,455 +0,0 @@
---
- name: Registering container name
set_fact:
cname: "{{ cname_prefix ~ '-hi' }}"
- name: Registering container name
set_fact:
cnames: "{{ cnames + [cname] }}"
####################################################################
## Creation ########################################################
####################################################################
- name: Create container (check)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: present
check_mode: yes
register: create_1
- name: Create container
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: present
register: create_2
- name: Create container (idempotent)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: present
register: create_3
- name: Create container (idempotent check)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: present
check_mode: yes
register: create_4
- assert:
that:
- create_1 is changed
- create_2 is changed
- create_3 is not changed
- create_4 is not changed
####################################################################
## Starting (after creation) #######################################
####################################################################
- name: Start container (check)
docker_container:
name: "{{ cname }}"
state: started
check_mode: yes
register: start_1
- name: Start container
docker_container:
name: "{{ cname }}"
state: started
register: start_2
- name: Start container (idempotent)
docker_container:
name: "{{ cname }}"
state: started
register: start_3
- name: Start container (idempotent check)
docker_container:
name: "{{ cname }}"
state: started
check_mode: yes
register: start_4
- assert:
that:
- start_1 is changed
- start_2 is changed
- start_3 is not changed
- start_4 is not changed
####################################################################
## Present check for running container #############################
####################################################################
- name: Present check for running container (check)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: present
check_mode: yes
register: present_check_1
- name: Present check for running container
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: present
register: present_check_2
- assert:
that:
- present_check_1 is not changed
- present_check_2 is not changed
####################################################################
## Starting (from scratch) #########################################
####################################################################
- name: Remove container (setup for starting from scratch)
docker_container:
name: "{{ cname }}"
state: absent
force_kill: yes
- name: Start container from scratch (check)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
stop_timeout: 1
name: "{{ cname }}"
state: started
check_mode: yes
register: start_scratch_1
- name: Start container from scratch
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
stop_timeout: 1
name: "{{ cname }}"
state: started
register: start_scratch_2
- name: Start container from scratch (idempotent)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
stop_timeout: 1
name: "{{ cname }}"
state: started
register: start_scratch_3
- name: Start container from scratch (idempotent check)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
stop_timeout: 1
name: "{{ cname }}"
state: started
check_mode: yes
register: start_scratch_4
- assert:
that:
- start_scratch_1 is changed
- start_scratch_2 is changed
- start_scratch_3 is not changed
- start_scratch_4 is not changed
####################################################################
## Recreating ######################################################
####################################################################
- name: Recreating container (created)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: present
force_kill: yes
register: recreate_1
- name: Recreating container (created, recreate, check mode)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
recreate: yes
state: present
force_kill: yes
register: recreate_2
check_mode: yes
- name: Recreating container (created, recreate)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
recreate: yes
state: present
force_kill: yes
register: recreate_3
- name: Recreating container (started)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
force_kill: yes
register: recreate_4
- name: Recreating container (started, recreate, check mode)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
recreate: yes
removal_wait_timeout: 10
state: started
force_kill: yes
register: recreate_5
check_mode: yes
- name: Recreating container (started, recreate)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
recreate: yes
removal_wait_timeout: 10
state: started
force_kill: yes
register: recreate_6
- name: cleanup
docker_container:
name: "{{ cname }}"
state: absent
force_kill: yes
diff: no
- debug: var=recreate_1
- debug: var=recreate_3
- debug: var=recreate_4
- debug: var=recreate_6
- assert:
that:
- recreate_2 is changed
- recreate_3 is changed
- recreate_4 is changed
- recreate_5 is changed
- recreate_6 is changed
- recreate_1.container.Id == recreate_2.container.Id
- recreate_1.container.Id != recreate_3.container.Id
- recreate_3.container.Id == recreate_4.container.Id
- recreate_4.container.Id == recreate_5.container.Id
- recreate_4.container.Id != recreate_6.container.Id
####################################################################
## Restarting ######################################################
####################################################################
- name: Restarting
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
stop_timeout: 1
volumes:
- /tmp/tmp
register: restart_1
- name: Restarting (restart, check mode)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
restart: yes
state: started
stop_timeout: 1
force_kill: yes
register: restart_2
check_mode: yes
- name: Restarting (restart)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
restart: yes
state: started
stop_timeout: 1
force_kill: yes
register: restart_3
- name: Restarting (verify volumes)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
stop_timeout: 1
volumes:
- /tmp/tmp
register: restart_4
- name: cleanup
docker_container:
name: "{{ cname }}"
state: absent
force_kill: yes
diff: no
- assert:
that:
- restart_1 is changed
- restart_2 is changed
- restart_3 is changed
- restart_1.container.Id == restart_3.container.Id
- restart_4 is not changed
####################################################################
## Stopping ########################################################
####################################################################
- name: Stop container (check)
docker_container:
image: alpine:3.8
name: "{{ cname }}"
state: stopped
stop_timeout: 1
check_mode: yes
register: stop_1
- name: Stop container
docker_container:
image: alpine:3.8
name: "{{ cname }}"
state: stopped
stop_timeout: 1
register: stop_2
- name: Stop container (idempotent)
docker_container:
image: alpine:3.8
name: "{{ cname }}"
state: stopped
stop_timeout: 1
register: stop_3
- name: Stop container (idempotent check)
docker_container:
image: alpine:3.8
name: "{{ cname }}"
state: stopped
stop_timeout: 1
check_mode: yes
register: stop_4
- assert:
that:
- stop_1 is changed
- stop_2 is changed
- stop_3 is not changed
- stop_4 is not changed
####################################################################
## Removing ########################################################
####################################################################
- name: Remove container (check)
docker_container:
name: "{{ cname }}"
state: absent
check_mode: yes
register: remove_1
- name: Remove container
docker_container:
name: "{{ cname }}"
state: absent
register: remove_2
- name: Remove container (idempotent)
docker_container:
name: "{{ cname }}"
state: absent
register: remove_3
- name: Remove container (idempotent check)
docker_container:
name: "{{ cname }}"
state: absent
check_mode: yes
register: remove_4
- assert:
that:
- remove_1 is changed
- remove_2 is changed
- remove_3 is not changed
- remove_4 is not changed
####################################################################
## Removing (from running) #########################################
####################################################################
- name: Start container (setup for removing from running)
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
- name: Remove container from running (check)
docker_container:
name: "{{ cname }}"
state: absent
force_kill: yes
check_mode: yes
register: remove_from_running_1
- name: Remove container from running
docker_container:
name: "{{ cname }}"
state: absent
force_kill: yes
register: remove_from_running_2
- name: Remove container from running (idempotent)
docker_container:
name: "{{ cname }}"
state: absent
force_kill: yes
register: remove_from_running_3
- name: Remove container from running (idempotent check)
docker_container:
name: "{{ cname }}"
state: absent
force_kill: yes
check_mode: yes
register: remove_from_running_4
- assert:
that:
- remove_from_running_1 is changed
- remove_from_running_2 is changed
- remove_from_running_3 is not changed
- remove_from_running_4 is not changed

View file

@ -1,7 +0,0 @@
disabled # tests already running in community.docker
shippable/posix/group2
skip/osx
skip/macos
skip/freebsd
skip/aix
destructive

View file

@ -1,3 +0,0 @@
---
dependencies:
- setup_docker

View file

@ -1,80 +0,0 @@
---
####################################################################
# WARNING: These are designed specifically for Ansible tests #
# and should not be used as examples of how to write Ansible roles #
####################################################################
- block:
- name: Create random container name
set_fact:
cname: "{{ 'ansible-test-%0x' % ((2**32) | random) }}"
- name: Make sure container is not there
docker_container:
name: "{{ cname }}"
state: absent
force_kill: yes
- name: Inspect a non-present container
docker_container_info:
name: "{{ cname }}"
register: result
- assert:
that:
- "not result.exists"
- "'container' in result"
- "result.container is none"
- name: Make sure container exists
docker_container:
name: "{{ cname }}"
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
state: started
force_kill: yes
- name: Inspect a present container
docker_container_info:
name: "{{ cname }}"
register: result
- name: Dump docker_container_info result
debug: var=result
- name: "Comparison: use 'docker inspect'"
command: docker inspect "{{ cname }}"
register: docker_inspect
ignore_errors: yes
- block:
- set_fact:
docker_inspect_result: "{{ docker_inspect.stdout | from_json }}"
- name: Dump docker inspect result
debug: var=docker_inspect_result
when: docker_inspect is not failed
- assert:
that:
- result.exists
- "'container' in result"
- "result.container"
- assert:
that:
- "result.container == docker_inspect_result[0]"
when: docker_inspect is not failed
- assert:
that:
- "'is too new. Maximum supported API version is' in docker_inspect.stderr"
when: docker_inspect is failed
always:
- name: Cleanup
docker_container:
name: "{{ cname }}"
state: absent
force_kill: yes
when: docker_py_version is version('1.8.0', '>=') and docker_api_version is version('1.20', '>=')
- fail: msg="Too old docker / docker-py version to run docker_container_info tests!"
when: not(docker_py_version is version('1.8.0', '>=') and docker_api_version is version('1.20', '>=')) and (ansible_distribution != 'CentOS' or ansible_distribution_major_version|int > 6)

View file

@ -1,6 +0,0 @@
shippable/posix/group2
skip/aix
skip/osx
skip/macos
skip/freebsd
destructive

View file

@ -1,3 +0,0 @@
---
dependencies:
- setup_docker

View file

@ -1,10 +0,0 @@
####################################################################
# WARNING: These are designed specifically for Ansible tests #
# and should not be used as examples of how to write Ansible roles #
####################################################################
- include_tasks: test_host_info.yml
when: docker_py_version is version('1.10.0', '>=') and docker_api_version is version('1.21', '>=')
- fail: msg="Too old docker / docker-py version to run docker_host_info tests!"
when: not(docker_py_version is version('1.10.0', '>=') and docker_api_version is version('1.21', '>=')) and (ansible_distribution != 'CentOS' or ansible_distribution_major_version|int > 6)

View file

@ -1,296 +0,0 @@
---
- name: Create random container/volume name
set_fact:
cname: "{{ 'ansible-test-%0x' % ((2**32) | random) }}"
vname: "{{ 'ansible-test-%0x' % ((2**32) | random) }}"
- debug:
msg: "Using container name '{{ cname }}' and volume name '{{ vname }}'"
- block:
- name: Get info on Docker host
docker_host_info:
register: output
- name: assert reading docker host facts when docker is running
assert:
that:
- 'output.host_info.Name is string'
- 'output.containers is not defined'
- 'output.networks is not defined'
- 'output.volumes is not defined'
- 'output.images is not defined'
- 'output.disk_usage is not defined'
# Container and volume are created so that all lists are non-empty:
# * container and volume lists are non-emtpy because of the created objects;
# * image list is non-empty because the image of the container is there;
# * network list is always non-empty (default networks).
- name: Create container
docker_container:
image: alpine:3.8
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
register: container_output
- assert:
that:
- container_output is changed
- name: Create a volume
docker_volume:
name: "{{ vname }}"
register: volume_output
- assert:
that:
- volume_output is changed
- name: Get info on Docker host and list containers
docker_host_info:
containers: yes
register: output
- name: assert reading docker host facts when docker is running and list containers
assert:
that:
- 'output.host_info.Name is string'
- 'output.networks is not defined'
- 'output.volumes is not defined'
- 'output.images is not defined'
- 'output.disk_usage is not defined'
- 'output.containers[0].Image is string'
- 'output.containers[0].ImageID is not defined'
- name: Get info on Docker host and list containers with verbose output
docker_host_info:
containers: yes
verbose_output: yes
register: output
- name: assert reading docker host facts when docker is running and list containers with verbose output
assert:
that:
- 'output.host_info.Name is string'
- 'output.networks is not defined'
- 'output.volumes is not defined'
- 'output.images is not defined'
- 'output.disk_usage is not defined'
- 'output.containers[0].Image is string'
- 'output.containers[0].ImageID is string'
- name: Get info on Docker host and list images
docker_host_info:
images: yes
register: output
- name: assert reading docker host facts when docker is running and list images
assert:
that:
- 'output.host_info.Name is string'
- 'output.containers is not defined'
- 'output.networks is not defined'
- 'output.volumes is not defined'
- 'output.images[0].Id is string'
- 'output.images[0].ParentId is not defined'
- 'output.disk_usage is not defined'
- name: Get info on Docker host and list images with verbose output
docker_host_info:
images: yes
verbose_output: yes
register: output
- name: assert reading docker host facts when docker is running and list images with verbose output
assert:
that:
- 'output.host_info.Name is string'
- 'output.containers is not defined'
- 'output.networks is not defined'
- 'output.volumes is not defined'
- 'output.images[0].Id is string'
- 'output.images[0].ParentId is string'
- 'output.disk_usage is not defined'
- name: Get info on Docker host and list networks
docker_host_info:
networks: yes
register: output
- name: assert reading docker host facts when docker is running and list networks
assert:
that:
- 'output.host_info.Name is string'
- 'output.containers is not defined'
- 'output.networks[0].Id is string'
- 'output.networks[0].Created is not defined'
- 'output.volumes is not defined'
- 'output.images is not defined'
- 'output.disk_usage is not defined'
- name: Get info on Docker host and list networks with verbose output
docker_host_info:
networks: yes
verbose_output: yes
register: output
- name: assert reading docker host facts when docker is running and list networks with verbose output
assert:
that:
- 'output.host_info.Name is string'
- 'output.containers is not defined'
- 'output.networks[0].Id is string'
- 'output.networks[0].Created is string'
- 'output.volumes is not defined'
- 'output.images is not defined'
- 'output.disk_usage is not defined'
- name: Get info on Docker host and list volumes
docker_host_info:
volumes: yes
register: output
- name: assert reading docker host facts when docker is running and list volumes
assert:
that:
- 'output.host_info.Name is string'
- 'output.containers is not defined'
- 'output.networks is not defined'
- 'output.volumes[0].Name is string'
- 'output.volumes[0].Mountpoint is not defined'
- 'output.images is not defined'
- 'output.disk_usage is not defined'
- name: Get info on Docker host and list volumes with verbose output
docker_host_info:
volumes: yes
verbose_output: yes
register: output
- name: assert reading docker host facts when docker is running and list volumes with verbose output
assert:
that:
- 'output.host_info.Name is string'
- 'output.containers is not defined'
- 'output.networks is not defined'
- 'output.volumes[0].Name is string'
- 'output.volumes[0].Mountpoint is string'
- 'output.images is not defined'
- 'output.disk_usage is not defined'
- name: Get info on Docker host and get disk usage
docker_host_info:
disk_usage: yes
register: output
ignore_errors: yes
- name: assert reading docker host facts when docker is running and get disk usage
assert:
that:
- 'output.host_info.Name is string'
- 'output.containers is not defined'
- 'output.networks is not defined'
- 'output.volumes is not defined'
- 'output.images is not defined'
- 'output.disk_usage.LayersSize is number'
- 'output.disk_usage.BuilderSize is not defined'
when: docker_py_version is version('2.2.0', '>=')
- assert:
that:
- output is failed
- "('version is ' ~ docker_py_version ~ ' ') in output.msg"
- "'Minimum version required is 2.2.0 ' in output.msg"
when: docker_py_version is version('2.2.0', '<')
- name: Get info on Docker host and get disk usage with verbose output
docker_host_info:
disk_usage: yes
verbose_output: yes
register: output
ignore_errors: yes
- name: assert reading docker host facts when docker is running and get disk usage with verbose output
assert:
that:
- 'output.host_info.Name is string'
- 'output.containers is not defined'
- 'output.networks is not defined'
- 'output.volumes is not defined'
- 'output.images is not defined'
- 'output.disk_usage.LayersSize is number'
- 'output.disk_usage.BuilderSize is number'
when: docker_py_version is version('2.2.0', '>=')
- assert:
that:
- output is failed
- "('version is ' ~ docker_py_version ~ ' ') in output.msg"
- "'Minimum version required is 2.2.0 ' in output.msg"
when: docker_py_version is version('2.2.0', '<')
- name: Get info on Docker host, disk usage and get all lists together
docker_host_info:
volumes: yes
containers: yes
networks: yes
images: yes
disk_usage: "{{ docker_py_version is version('2.2.0', '>=') }}"
register: output
- name: assert reading docker host facts when docker is running, disk usage and get lists together
assert:
that:
- 'output.host_info.Name is string'
- 'output.containers[0].Image is string'
- 'output.containers[0].ImageID is not defined'
- 'output.networks[0].Id is string'
- 'output.networks[0].Created is not defined'
- 'output.volumes[0].Name is string'
- 'output.volumes[0].Mountpoint is not defined'
- 'output.images[0].Id is string'
- 'output.images[0].ParentId is not defined'
- assert:
that:
- 'output.disk_usage.LayersSize is number'
- 'output.disk_usage.BuilderSize is not defined'
when: docker_py_version is version('2.2.0', '>=')
- name: Get info on Docker host, disk usage and get all lists together with verbose output
docker_host_info:
volumes: yes
containers: yes
networks: yes
images: yes
disk_usage: "{{ docker_py_version is version('2.2.0', '>=') }}"
verbose_output: yes
register: output
- name: assert reading docker host facts when docker is running and get disk usage with verbose output
assert:
that:
- 'output.host_info.Name is string'
- 'output.containers[0].Image is string'
- 'output.containers[0].ImageID is string'
- 'output.networks[0].Id is string'
- 'output.networks[0].Created is string'
- 'output.volumes[0].Name is string'
- 'output.volumes[0].Mountpoint is string'
- 'output.images[0].Id is string'
- 'output.images[0].ParentId is string'
- assert:
that:
- 'output.disk_usage.LayersSize is number'
- 'output.disk_usage.BuilderSize is number'
when: docker_py_version is version('2.2.0', '>=')
always:
- name: Delete container
docker_container:
name: "{{ cname }}"
state: absent
force_kill: yes
- name: Delete volume
docker_volume:
name: "{{ vname }}"
state: absent

View file

@ -1,7 +0,0 @@
disabled # tests already running in community.docker
shippable/posix/group5
skip/aix
skip/osx
skip/macos
skip/freebsd
destructive

View file

@ -1,3 +0,0 @@
FROM busybox
ENV foo /bar
WORKDIR ${foo}

View file

@ -1,3 +0,0 @@
FROM busybox
# This should fail building if docker cannot resolve some-custom-host
RUN ping -c1 some-custom-host

View file

@ -1,5 +0,0 @@
FROM alpine:3.7
ENV INSTALL_PATH /newdata
RUN mkdir -p $INSTALL_PATH
WORKDIR $INSTALL_PATH

View file

@ -1,7 +0,0 @@
FROM busybox AS first
ENV dir /first
WORKDIR ${dir}
FROM busybox AS second
ENV dir /second
WORKDIR ${dir}

View file

@ -1,3 +0,0 @@
---
dependencies:
- setup_docker_registry

View file

@ -1,8 +0,0 @@
####################################################################
# WARNING: These are designed specifically for Ansible tests #
# and should not be used as examples of how to write Ansible roles #
####################################################################
- when: ansible_facts.distribution ~ ansible_facts.distribution_major_version not in ['CentOS6', 'RedHat6']
include_tasks:
file: test.yml

View file

@ -1,3 +0,0 @@
---
- name: "Loading tasks from {{ item }}"
include_tasks: "{{ item }}"

View file

@ -1,34 +0,0 @@
---
- name: Create random name prefix
set_fact:
name_prefix: "{{ 'ansible-test-%0x' % ((2**32) | random) }}"
- name: Create image and container list
set_fact:
inames: []
cnames: []
- debug:
msg: "Using name prefix {{ name_prefix }}"
- block:
- include_tasks: run-test.yml
with_fileglob:
- "tests/*.yml"
always:
- name: "Make sure all images are removed"
docker_image:
name: "{{ item }}"
state: absent
with_items: "{{ inames }}"
- name: "Make sure all containers are removed"
docker_container:
name: "{{ item }}"
state: absent
force_kill: yes
with_items: "{{ cnames }}"
when: docker_py_version is version('1.8.0', '>=') and docker_api_version is version('1.20', '>=')
- fail: msg="Too old docker / docker-py version to run docker_image tests!"
when: not(docker_py_version is version('1.8.0', '>=') and docker_api_version is version('1.20', '>=')) and (ansible_distribution != 'CentOS' or ansible_distribution_major_version|int > 6)

View file

@ -1,78 +0,0 @@
---
####################################################################
## basic ###########################################################
####################################################################
- name: Make sure image is not there
docker_image:
name: "hello-world:latest"
state: absent
force_absent: yes
register: absent_1
- name: Make sure image is not there (idempotency)
docker_image:
name: "hello-world:latest"
state: absent
register: absent_2
- assert:
that:
- absent_2 is not changed
- name: Make sure image is there
docker_image:
name: "hello-world:latest"
state: present
source: pull
register: present_1
- name: Make sure image is there (idempotent)
docker_image:
name: "hello-world:latest"
state: present
source: pull
register: present_2
- assert:
that:
- present_1 is changed
- present_2 is not changed
- name: Make sure tag is not there
docker_image:
name: "hello-world:alias"
state: absent
- name: Tag image with alias
docker_image:
source: local
name: "hello-world:latest"
repository: "hello-world:alias"
register: tag_1
- name: Tag image with alias (idempotent)
docker_image:
source: local
name: "hello-world:latest"
repository: "hello-world:alias"
register: tag_2
- name: Tag image with alias (force, still idempotent)
docker_image:
source: local
name: "hello-world:latest"
repository: "hello-world:alias"
force_tag: yes
register: tag_3
- assert:
that:
- tag_1 is changed
- tag_2 is not changed
- tag_3 is not changed
- name: Cleanup alias tag
docker_image:
name: "hello-world:alias"
state: absent

View file

@ -1,157 +0,0 @@
---
- name: Registering image name
set_fact:
iname: "{{ name_prefix ~ '-options' }}"
- name: Determining pushed image names
set_fact:
hello_world_image_base: "{{ registry_address }}/test/hello-world"
test_image_base: "{{ registry_address }}/test/{{ iname }}"
- name: Registering image name
set_fact:
inames: "{{ inames + [iname, test_image_base ~ ':latest', hello_world_image_base ~ ':latest'] }}"
####################################################################
## interact with test registry #####################################
####################################################################
- name: Make sure image is not there
docker_image:
name: "{{ hello_world_image_base }}:latest"
state: absent
force_absent: yes
- name: Make sure we have hello-world:latest
docker_image:
name: hello-world:latest
source: pull
- name: Push image to test registry
docker_image:
name: "hello-world:latest"
repository: "{{ hello_world_image_base }}"
push: yes
source: local
register: push_1
- name: Push image to test registry (idempotent)
docker_image:
name: "hello-world:latest"
repository: "{{ hello_world_image_base }}"
push: yes
source: local
register: push_2
- name: Push image to test registry (force, still idempotent)
docker_image:
name: "hello-world:latest"
repository: "{{ hello_world_image_base }}"
push: yes
source: local
force_tag: yes
register: push_3
- assert:
that:
- push_1 is changed
- push_2 is not changed
- push_3 is not changed
- name: Get facts of local image
docker_image_info:
name: "{{ hello_world_image_base }}:latest"
register: facts_1
- name: Make sure image is not there
docker_image:
name: "{{ hello_world_image_base }}:latest"
state: absent
force_absent: yes
- name: Get facts of local image (absent)
docker_image_info:
name: "{{ hello_world_image_base }}:latest"
register: facts_2
- name: Pull image from test registry
docker_image:
name: "{{ hello_world_image_base }}:latest"
state: present
source: pull
register: pull_1
- name: Pull image from test registry (idempotency)
docker_image:
name: "{{ hello_world_image_base }}:latest"
state: present
source: pull
register: pull_2
- name: Get facts of local image (present)
docker_image_info:
name: "{{ hello_world_image_base }}:latest"
register: facts_3
- assert:
that:
- pull_1 is changed
- pull_2 is not changed
- facts_1.images | length == 1
- facts_2.images | length == 0
- facts_3.images | length == 1
####################################################################
## repository ######################################################
####################################################################
- name: Make sure image is not there
docker_image:
name: "{{ test_image_base }}:latest"
state: absent
force_absent: yes
- name: repository
docker_image:
name: "{{ iname }}"
build:
path: "{{ role_path }}/files"
pull: no
repository: "{{ test_image_base }}"
source: build
register: repository_1
- name: repository (idempotent)
docker_image:
name: "{{ iname }}"
build:
path: "{{ role_path }}/files"
pull: no
repository: "{{ test_image_base }}"
source: build
register: repository_2
- assert:
that:
- repository_1 is changed
- repository_2 is not changed
# Uncomment in community.docker
# - assert:
# that:
# - 'FROM busybox' in repository_1.stdout
- name: Get facts of image
docker_image_info:
name: "{{ test_image_base }}:latest"
register: facts_1
- name: cleanup
docker_image:
name: "{{ test_image_base }}:latest"
state: absent
force_absent: yes
- assert:
that:
- facts_1.images | length == 1

Some files were not shown because too many files have changed in this diff Show more