Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add docker_image_load module #90

Merged
merged 13 commits into from
Mar 5, 2021
Merged
186 changes: 186 additions & 0 deletions plugins/modules/docker_image_load.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
#!/usr/bin/python
felixfontein marked this conversation as resolved.
Show resolved Hide resolved
#
# 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_load

short_description: Load docker image(s) from archives

version_added: 1.3.0

description:
- Load one or multiple Docker images from a C(.tar) archive, and return information on
the loaded image(s).

options:
path:
description:
- The path to the C(.tar) archive to load Docker image(s) from.
type: path
required: true

extends_documentation_fragment:
- community.docker.docker
- community.docker.docker.docker_py_2_documentation

notes:
- Does not support C(check_mode).

requirements:
- "L(Docker SDK for Python,https://docker-py.readthedocs.io/en/stable/) >= 2.5.0"
- "Docker API >= 1.23"

author:
- Felix Fontein (@felixfontein)
'''

EXAMPLES = '''
- name: Load all image(s) from the given tar file
community.docker.docker_image_load:
path: /path/to/images.tar
register: result

- name: Print the loaded image names
ansible.builtin.debug:
msg: "Loaded the following images: {{ result.image_names | join(', ') }}"
'''

RETURN = '''
image_names:
description: List of image names and IDs loaded from the archive.
returned: success
type: list
elements: str
sample:
- 'hello-world:latest'
- 'sha256:e004c2cc521c95383aebb1fb5893719aa7a8eae2e7a71f316a4410784edb00a9'
images:
description: Image inspection results for the loaded images.
returned: success
type: list
elements: dict
sample: []
Andersson007 marked this conversation as resolved.
Show resolved Hide resolved
'''

import errno
import traceback

from ansible_collections.community.docker.plugins.module_utils.common import (
AnsibleDockerClient,
DockerBaseClass,
is_image_name_id,
RequestException,
)

try:
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.path = parameters['path']

self.load_images()

@staticmethod
def _extract_output_line(line, output):
'''
Extract text line from stream output and, if found, adds it to output.
'''
if 'stream' in line or 'status' in line:
# Make sure we have a string (assuming that line['stream'] and
# line['status'] are either not defined, falsish, or a string)
text_line = line.get('stream') or line.get('status') or ''
output.extend(text_line.splitlines())

def load_images(self):
'''
Load images from a .tar archive
'''
# Load image(s) from file
load_output = []
try:
self.log("Opening image {0}".format(self.path))
with open(self.path, 'rb') as image_tar:
self.log("Loading images from {0}".format(self.path))
for line in self.client.load_image(image_tar):
self.log(line, pretty_print=True)
self._extract_output_line(line, load_output)
except EnvironmentError as exc:
if exc.errno == errno.ENOENT:
self.client.fail("Error opening archive {0} - {1}".format(self.path, str(exc)))
self.client.fail("Error loading archive {0} - {1}".format(self.path, str(exc)), stdout='\n'.join(load_output))
except Exception as exc:
self.client.fail("Error loading archive {0} - {1}".format(self.path, str(exc)), stdout='\n'.join(load_output))

# Collect loaded images
loaded_images = []
for line in load_output:
if line.startswith('Loaded image:'):
loaded_images.append(line[len('Loaded image:'):].strip())
if line.startswith('Loaded image ID:'):
loaded_images.append(line[len('Loaded image ID:'):].strip())

if not loaded_images:
self.client.fail("Detected no loaded images. Archive potentially corrupt?", stdout='\n'.join(load_output))

images = []
for image_name in loaded_images:
if is_image_name_id(image_name):
images.append(self.client.find_image_by_id(image_name))
elif ':' in image_name:
image_name, tag = image_name.rsplit(':', 1)
images.append(self.client.find_image(image_name, tag))
else:
self.client.module.warn('Image name "{0}" is neither ID nor has a tag'.format(image_name))

self.results['image_names'] = loaded_images
self.results['images'] = images
self.results['changed'] = True
self.results['stdout'] = '\n'.join(load_output)


def main():
client = AnsibleDockerClient(
argument_spec=dict(
path=dict(type='path', required=True),
),
supports_check_mode=False,
min_docker_version='2.5.0',
min_docker_api_version='1.23',
)

try:
results = dict(
image_names=[],
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()
2 changes: 2 additions & 0 deletions tests/integration/targets/docker_image_load/aliases
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
shippable/posix/group4
destructive
3 changes: 3 additions & 0 deletions tests/integration/targets/docker_image_load/meta/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
dependencies:
- setup_docker
8 changes: 8 additions & 0 deletions tests/integration/targets/docker_image_load/tasks/main.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
####################################################################
# 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
- name: "Loading tasks from {{ item }}"
include_tasks: "{{ item }}"
34 changes: 34 additions & 0 deletions tests/integration/targets/docker_image_load/tasks/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
- 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('2.5.0', '>=') and docker_api_version is version('1.23', '>=')

- fail: msg="Too old docker / docker-py version to run docker_image tests!"
when: not(docker_py_version is version('2.5.0', '>=') and docker_api_version is version('1.23', '>=')) and (ansible_distribution != 'CentOS' or ansible_distribution_major_version|int > 6)
Loading