Add docker_context_info module (#1039)

* Vendor parts of the Docker SDK for Python

This is a combination of the latest git version
(https://github.com/docker/docker-py/tree/db7f8b8bb67e485a7192846906f600a52e0aa623)
with some fixes to make it compatible with Python 2.7
and adjusting some imports.

* Polishing.

* Fix bug that prevents contexts to be found when no Docker config file is present.

Ref: https://github.com/docker/docker-py/issues/3190

* Linting.

* Fix typos.

* Adjust more to behavior of Docker CLI.

* Add first iteration of docker_context_info module.

* Improvements.

* Add basic CI.

* Add caveat on contexts[].config result.
This commit is contained in:
Felix Fontein
2025-02-10 21:59:05 +01:00
committed by GitHub
parent ea3ac5f195
commit 3b6068e44b
16 changed files with 1180 additions and 11 deletions
@@ -0,0 +1,6 @@
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
azp/5
destructive
@@ -0,0 +1,8 @@
---
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
dependencies:
- setup_docker
- setup_docker_python_deps
@@ -0,0 +1,40 @@
---
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
####################################################################
# 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-docker-test-%0x' % ((2**32) | random) }}"
cnames: []
- debug:
msg: "Using container name prefix {{ cname_prefix }}"
# Run the tests
- block:
- include_tasks: run-test.yml
with_fileglob:
- "tests/*.yml"
loop_control:
loop_var: test_name
always:
- name: "Make sure all containers are removed"
docker_container:
name: "{{ item }}"
state: absent
force_kill: true
with_items: "{{ cnames }}"
diff: false
when: docker_api_version is version('1.25', '>=')
- fail: msg="Too old docker / docker-py version to run all docker_container tests!"
when: not(docker_api_version is version('1.25', '>=')) and (ansible_distribution != 'CentOS' or ansible_distribution_major_version|int > 6)
@@ -0,0 +1,7 @@
---
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
- name: "Loading tasks from {{ test_name }}"
include_tasks: "{{ test_name }}"
@@ -0,0 +1,121 @@
---
# Copyright (c) Ansible Project
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later
- name: Registering container name
set_fact:
cname: "{{ cname_prefix ~ '-hi' }}"
- name: Registering container name
set_fact:
cnames: "{{ cnames + [cname] }}"
- name: Get current context
community.docker.docker_context_info:
only_current: true
register: docker_current_context
- assert:
that:
- docker_current_context is not changed
# Some of the following tests will not be true on all machines, but they should be in CI:
- docker_current_context.current_context_name == 'default'
- docker_current_context.contexts | length == 1
- docker_current_context.contexts[0].name == 'default'
- docker_current_context.contexts[0].current == true
- docker_current_context.contexts[0].description == 'Current DOCKER_HOST based configuration'
- docker_current_context.contexts[0].meta_path is none
- docker_current_context.contexts[0].tls_path is none
- docker_current_context.contexts[0].config.docker_host == 'unix:///var/run/docker.sock'
- docker_current_context.contexts[0].config.tls == false
- name: Run community.docker modules with current context
module_defaults:
group/community.docker.docker: "{{ docker_current_context.contexts[0].config }}"
block:
- name: Create container
docker_container:
image: "{{ docker_test_image_alpine }}"
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: present
register: create_1
- name: Create container (idempotent)
docker_container:
image: "{{ docker_test_image_alpine }}"
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: present
register: create_2
- assert:
that:
- create_1 is changed
- create_2 is not changed
- name: Inspect container with CLI tool
ansible.builtin.command:
cmd: docker inspect {{ cname }}
register: result
- assert:
that:
- (result.stdout | from_json) | length == 1
- (result.stdout | from_json)[0].State.Status == "created"
- name: Start container
docker_container:
name: "{{ cname }}"
state: started
register: start_1
- name: Start container (idempotent)
docker_container:
name: "{{ cname }}"
state: started
register: start_2
- assert:
that:
- start_1 is changed
- start_2 is not changed
- name: Inspect container with CLI tool
ansible.builtin.command:
cmd: docker inspect {{ cname }}
register: result
- assert:
that:
- (result.stdout | from_json) | length == 1
- (result.stdout | from_json)[0].State.Status == "running"
- name: Remove container
docker_container:
name: "{{ cname }}"
state: absent
force_kill: true
register: remove_1
- name: Remove container (idempotent)
docker_container:
name: "{{ cname }}"
state: absent
force_kill: true
register: remove_2
- assert:
that:
- remove_1 is changed
- remove_2 is not changed
- name: Inspect container with CLI tool
ansible.builtin.command:
cmd: docker inspect {{ cname }}
register: result
failed_when: result.rc != 1
- assert:
that:
- (result.stdout | from_json) | length == 0
+1
View File
@@ -20,6 +20,7 @@ ACTION_GROUPS = {
'pattern': re.compile('^.*$'),
'exclusions': [
'current_container_facts',
'docker_context_info',
],
'doc_fragment': 'community.docker.attributes.actiongroup_docker',
},
@@ -0,0 +1,68 @@
# -*- coding: utf-8 -*-
# This code is part of the Ansible collection community.docker, but is an independent component.
# This particular file, and this file only, is based on the Docker SDK for Python (https://github.com/docker/docker-py/)
#
# Copyright (c) 2016-2025 Docker, Inc.
#
# It is licensed under the Apache 2.0 license (see LICENSES/Apache-2.0.txt in this collection)
# SPDX-License-Identifier: Apache-2.0
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
import unittest
import pytest
from ansible_collections.community.docker.plugins.module_utils._api import errors
from ansible_collections.community.docker.plugins.module_utils._api.constants import (
DEFAULT_NPIPE,
DEFAULT_UNIX_SOCKET,
IS_WINDOWS_PLATFORM,
)
from ansible_collections.community.docker.plugins.module_utils._api.context.api import ContextAPI
from ansible_collections.community.docker.plugins.module_utils._api.context.context import Context
class BaseContextTest(unittest.TestCase):
@pytest.mark.skipif(
IS_WINDOWS_PLATFORM, reason='Linux specific path check'
)
def test_url_compatibility_on_linux(self):
c = Context("test")
assert c.Host == DEFAULT_UNIX_SOCKET[5:]
@pytest.mark.skipif(
not IS_WINDOWS_PLATFORM, reason='Windows specific path check'
)
def test_url_compatibility_on_windows(self):
c = Context("test")
assert c.Host == DEFAULT_NPIPE
def test_fail_on_default_context_create(self):
with pytest.raises(errors.ContextException):
ContextAPI.create_context("default")
def test_default_in_context_list(self):
found = False
ctx = ContextAPI.contexts()
for c in ctx:
if c.Name == "default":
found = True
assert found is True
def test_get_current_context(self):
assert ContextAPI.get_current_context().Name == "default"
def test_https_host(self):
c = Context("test", host="tcp://testdomain:8080", tls=True)
assert c.Host == "https://testdomain:8080"
def test_context_inspect_without_params(self):
ctx = ContextAPI.inspect_context()
assert ctx["Name"] == "default"
assert ctx["Metadata"]["StackOrchestrator"] == "swarm"
assert ctx["Endpoints"]["docker"]["Host"] in (
DEFAULT_NPIPE,
DEFAULT_UNIX_SOCKET[5:],
)
@@ -255,7 +255,7 @@ class ParseEnvFileTest(unittest.TestCase):
class ParseHostTest(unittest.TestCase):
def test_parse_host(self):
invalid_hosts = [
'0.0.0.0',
'foo://0.0.0.0',
'tcp://',
'udp://127.0.0.1',
'udp://127.0.0.1:2375',