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 image_label_mismatch parameter to docker_container #370

Merged
merged 12 commits into from
May 24, 2022
2 changes: 2 additions & 0 deletions changelogs/fragments/370-add-image-label-mismatch.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
minor_changes:
- "docker_container - added ``image_label_mismatch`` parameter (https://github.com/ansible-collections/community.docker/issues/314, https://github.com/ansible-collections/community.docker/pull/370)."
53 changes: 52 additions & 1 deletion plugins/modules/docker_container.py
Original file line number Diff line number Diff line change
Expand Up @@ -394,6 +394,22 @@
- Can also be an image ID. If this is the case, the image is assumed to be available locally.
The I(pull) option is ignored for this case.
type: str
image_label_mismatch:
description:
- How to handle labels inherited from the image that are not set explicitly.
- When C(ignore), labels that are present in the image but not specified in I(labels) will be
ignored. This is useful to avoid having to specify the image labels in I(labels) while keeping
labels I(comparisons) C(strict).
- When C(fail), if there are labels present in the image which are not set from I(labels), the
module will fail. This prevents introducing unexpected labels from the base image.
- "B(Warning:) This option is ignored unless C(labels: strict) or C(*: strict) is specified in
the I(comparisons) option."
type: str
choices:
- 'ignore'
- 'fail'
RoGryza marked this conversation as resolved.
Show resolved Hide resolved
default: ignore
version_added: 2.6.0
init:
description:
- Run an init inside the container that forwards signals and reaps processes.
Expand Down Expand Up @@ -2167,6 +2183,7 @@ def __init__(self, container, parameters):
self.parameters.expected_env = None
self.parameters.expected_device_requests = None
self.parameters_map = dict()
self.parameters_map['expected_labels'] = 'labels'
self.parameters_map['expected_links'] = 'links'
self.parameters_map['expected_ports'] = 'expected_ports'
self.parameters_map['expected_exposed'] = 'exposed_ports'
Expand All @@ -2177,6 +2194,7 @@ def __init__(self, container, parameters):
self.parameters_map['expected_env'] = 'env'
self.parameters_map['expected_entrypoint'] = 'entrypoint'
self.parameters_map['expected_binds'] = 'volumes'
self.parameters_map['expected_labels'] = 'labels'
self.parameters_map['expected_cmd'] = 'command'
self.parameters_map['expected_devices'] = 'devices'
self.parameters_map['expected_healthcheck'] = 'healthcheck'
Expand Down Expand Up @@ -2248,6 +2266,7 @@ def has_different_configuration(self, image):
self.parameters.expected_exposed = self._get_expected_exposed(image)
self.parameters.expected_volumes = self._get_expected_volumes(image)
self.parameters.expected_binds = self._get_expected_binds(image)
self.parameters.expected_labels = self._get_expected_labels(image)
self.parameters.expected_ulimits = self._get_expected_ulimits(self.parameters.ulimits)
self.parameters.expected_sysctls = self._get_expected_sysctls(self.parameters.sysctls)
self.parameters.expected_etc_hosts = self._convert_simple_dict_to_list('etc_hosts')
Expand Down Expand Up @@ -2301,7 +2320,7 @@ def has_different_configuration(self, image):
expected_exposed=expected_exposed,
groups=host_config.get('GroupAdd'),
ipc_mode=host_config.get("IpcMode"),
labels=config.get('Labels'),
expected_labels=config.get('Labels'),
expected_links=host_config.get('Links'),
mac_address=config.get('MacAddress', network.get('MacAddress')),
memory_swappiness=host_config.get('MemorySwappiness'),
Expand Down Expand Up @@ -2403,6 +2422,20 @@ def has_different_configuration(self, image):
# expected_healthcheck comparison in this case.
continue

if key == 'expected_labels' and compare['comparison'] == 'strict' and self.parameters.image_label_mismatch == 'fail':
# If there are labels from the base image that should be removed and
# base_image_mismatch is fail we want raise an error.
image_labels = self._get_image_labels(image)
would_remove_labels = []
for label in image_labels:
if label not in self.parameters.labels:
# Format label for error message
would_remove_labels.append(label)
if would_remove_labels:
msg = ("Some labels should be removed but are present in the base image. You can set image_label_mismatch to 'ignore' to ignore"
" this error. Labels: {0}")
self.fail(msg.format(', '.join(['"%s"' % label for label in would_remove_labels])))

# no match. record the differences
p = getattr(self.parameters, key)
c = value
Expand Down Expand Up @@ -2641,6 +2674,23 @@ def _get_expected_binds(self, image):
self.log(result, pretty_print=True)
return result

def _get_expected_labels(self, image):
if self.parameters.labels is None:
return None
if self.parameters.image_label_mismatch == 'ignore':
RoGryza marked this conversation as resolved.
Show resolved Hide resolved
expected_labels = dict(self._get_image_labels(image))
else:
expected_labels = {}
expected_labels.update(self.parameters.labels)
return expected_labels

def _get_image_labels(self, image):
if not image:
return {}

# Can't use get('Labels', {}) because 'Labels' may be present and be None
return image[self.parameters.client.image_inspect_source].get('Labels') or {}

def _get_expected_device_requests(self):
if self.parameters.device_requests is None:
return None
Expand Down Expand Up @@ -3552,6 +3602,7 @@ def main():
hostname=dict(type='str'),
ignore_image=dict(type='bool', default=False),
image=dict(type='str'),
image_label_mismatch=dict(type='str', choices=['ignore', 'fail'], default='ignore'),
init=dict(type='bool'),
interactive=dict(type='bool'),
ipc_mode=dict(type='str'),
Expand Down
6 changes: 6 additions & 0 deletions tests/integration/targets/docker_container/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
set_fact:
cname_prefix: "{{ 'ansible-docker-test-%0x' % ((2**32) | random) }}"
cnames: []
inames: []
dnetworks: []

- debug:
Expand All @@ -41,6 +42,11 @@
force_kill: yes
with_items: "{{ cnames }}"
diff: no
- name: "Make sure all images are removed"
docker_image:
name: "{{ item }}"
state: absent
with_items: "{{ inames }}"
- name: "Make sure all networks are removed"
docker_network:
name: "{{ item }}"
Expand Down
163 changes: 163 additions & 0 deletions tests/integration/targets/docker_container/tasks/tests/options.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2194,6 +2194,169 @@
- ignore_image is not changed
- image_change is changed

####################################################################
## image_label_mismatch ############################################
####################################################################

- name: Registering image name
set_fact:
iname_labels: "{{ cname_prefix ~ '-labels' }}"
- name: Registering image name
set_fact:
inames: "{{ inames + [iname_labels] }}"
- name: build image with labels
command:
cmd: "docker build --label img_label=base --tag {{ iname_labels }} -"
stdin: "FROM {{ docker_test_image_alpine }}"

- name: image_label_mismatch
docker_container:
image: "{{ iname_labels }}"
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
state: started
register: image_label_mismatch_1

- name: image_label_mismatch (ignore,unmanaged labels)
docker_container:
image: "{{ iname_labels }}"
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
image_label_mismatch: ignore
state: started
register: image_label_mismatch_2

- name: image_label_mismatch (ignore,missing img label)
docker_container:
image: "{{ iname_labels }}"
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
image_label_mismatch: ignore
labels: {}
state: started
register: image_label_mismatch_3

- name: image_label_mismatch (ignore,match img label)
docker_container:
image: "{{ iname_labels }}"
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
image_label_mismatch: ignore
labels:
img_label: base
state: started
register: image_label_mismatch_4

- name: image_label_mismatch (ignore,mismatched img label)
docker_container:
image: "{{ iname_labels }}"
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
image_label_mismatch: ignore
labels:
img_label: override
state: started
felixfontein marked this conversation as resolved.
Show resolved Hide resolved
force_kill: yes
register: image_label_mismatch_5

- name: image_label_mismatch (ignore,remove img label)
docker_container:
image: "{{ iname_labels }}"
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
image_label_mismatch: ignore
labels: {}
state: started
felixfontein marked this conversation as resolved.
Show resolved Hide resolved
force_kill: yes
register: image_label_mismatch_6

- name: image_label_mismatch (fail,unmanaged labels)
docker_container:
image: "{{ iname_labels }}"
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
image_label_mismatch: fail
state: started
register: image_label_mismatch_7

- name: image_label_mismatch (fail,non-strict,missing img label)
docker_container:
image: "{{ iname_labels }}"
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
image_label_mismatch: fail
labels: {}
state: started
register: image_label_mismatch_8

- name: image_label_mismatch (fail,strict,missing img label)
docker_container:
image: "{{ iname_labels }}"
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
image_label_mismatch: fail
comparisons:
labels: strict
labels: {}
state: started
ignore_errors: yes
register: image_label_mismatch_9

- name: image_label_mismatch (fail,match img label)
docker_container:
image: "{{ iname_labels }}"
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
image_label_mismatch: fail
labels:
img_label: base
state: started
register: image_label_mismatch_10

- name: image_label_mismatch (fail,mismatched img label)
docker_container:
image: "{{ iname_labels }}"
command: '/bin/sh -c "sleep 10m"'
name: "{{ cname }}"
image_label_mismatch: fail
labels:
img_label: override
state: started
felixfontein marked this conversation as resolved.
Show resolved Hide resolved
force_kill: yes
register: image_label_mismatch_11

- name: cleanup container
docker_container:
name: "{{ cname }}"
state: absent
force_kill: yes
diff: no

- name: cleanup image
docker_image:
name: "{{ iname_labels }}"
state: absent
diff: no

- assert:
that:
- image_label_mismatch_1 is changed
- image_label_mismatch_1.container.Config.Labels.img_label == "base"
- image_label_mismatch_2 is not changed
- image_label_mismatch_3 is not changed
- image_label_mismatch_4 is not changed
- image_label_mismatch_5 is changed
- image_label_mismatch_5.container.Config.Labels.img_label == "override"
- image_label_mismatch_6 is changed
- image_label_mismatch_6.container.Config.Labels.img_label == "base"
- image_label_mismatch_7 is not changed
- image_label_mismatch_8 is not changed
- image_label_mismatch_9 is failed
RoGryza marked this conversation as resolved.
Show resolved Hide resolved
- >-
image_label_mismatch_9.msg == ("Some labels should be removed but are present in the base image. You can set image_label_mismatch to 'ignore' to ignore this error. " ~ 'Labels: "img_label"')
- image_label_mismatch_10 is not changed
- image_label_mismatch_11 is changed

####################################################################
## ipc_mode ########################################################
####################################################################
Expand Down