docker_image(_push): fix push detection (#1199) (#1200)

* Fix IP address retrieval for registry setup.

* Adjust push detection to Docker 29.

* Idempotency for export no longer works.

* Disable pull idempotency checks that play with architecture.

* Add more known image IDs.

* Adjust load tests.

* Adjust error message check.

* Allow for more digests.

* Make sure a new enough cryptography version is installed.

(cherry picked from commit d207643e0c)
This commit is contained in:
Felix Fontein
2025-11-16 10:36:44 +01:00
committed by GitHub
parent a80e6bf7ec
commit 5cea1cdc6d
16 changed files with 125 additions and 37 deletions
@@ -256,6 +256,10 @@
- assert:
that:
- archive_image_2 is not changed
when: docker_cli_version is version("29.0.0", "<")
# Apparently idempotency no longer works with the default storage backend
# in Docker 29.0.0.
# https://github.com/ansible-collections/community.docker/pull/1199
- name: Archive image 3rd time, should overwrite due to different id
docker_image:
@@ -67,3 +67,7 @@
manifests_json: "{{ manifests.results | map(attribute='stdout') | map('from_json') }}"
manifest_json_images: "{{ item.2 | map(attribute='Config') | map('regex_replace', '.json$', '') | map('regex_replace', '^blobs/sha256/', '') | sort }}"
export_image_ids: "{{ item.1 | map('regex_replace', '^sha256:', '') | unique | sort }}"
when: docker_cli_version is version("29.0.0", "<")
# Apparently idempotency no longer works with the default storage backend
# in Docker 29.0.0.
# https://github.com/ansible-collections/community.docker/pull/1199
@@ -73,11 +73,17 @@
loop: "{{ all_images }}"
when: remove_all_images is failed
- name: Show all images
ansible.builtin.command: docker image ls
- name: Load all images (IDs)
docker_image_load:
path: "{{ remote_tmp_dir }}/archive-2.tar"
register: result
- name: Show all images
ansible.builtin.command: docker image ls
- name: Print loaded image names
debug:
var: result.image_names
@@ -110,11 +116,17 @@
name: "{{ item }}"
loop: "{{ all_images }}"
- name: Show all images
ansible.builtin.command: docker image ls
- name: Load all images (mixed images and IDs)
docker_image_load:
path: "{{ remote_tmp_dir }}/archive-3.tar"
register: result
- name: Show all images
ansible.builtin.command: docker image ls
- name: Print loading log
debug:
var: result.stdout_lines
@@ -127,10 +139,14 @@
that:
- result is changed
# For some reason, *sometimes* only the named image is found; in fact, in that case, the log only mentions that image and nothing else
- "result.images | length == 3 or ('Loaded image: ' ~ docker_test_image_hello_world) == result.stdout"
- (result.image_names | sort) in [[image_names[0], image_ids[0], image_ids[1]] | sort, [image_names[0]]]
- result.images | length in [1, 3]
- (result.images | map(attribute='Id') | sort) in [[image_ids[0], image_ids[0], image_ids[1]] | sort, [image_ids[0]]]
# With Docker 29, a third possibility appears: just two entries.
- >-
result.images | length == 3
or ('Loaded image: ' ~ docker_test_image_hello_world) == result.stdout
or result.images | length == 2
- (result.image_names | sort) in [[image_names[0], image_ids[0], image_ids[1]] | sort, [image_names[0], image_ids[1]] | sort, [image_names[0]]]
- result.images | length in [1, 2, 3]
- (result.images | map(attribute='Id') | sort) in [[image_ids[0], image_ids[0], image_ids[1]] | sort, [image_ids[0], image_ids[1]] | sort, [image_ids[0]]]
# Same image twice
@@ -139,11 +155,17 @@
name: "{{ item }}"
loop: "{{ all_images }}"
- name: Show all images
ansible.builtin.command: docker image ls
- name: Load all images (same image twice)
docker_image_load:
path: "{{ remote_tmp_dir }}/archive-4.tar"
register: result
- name: Show all images
ansible.builtin.command: docker image ls
- name: Print loaded image names
debug:
var: result.image_names
@@ -151,10 +173,11 @@
- assert:
that:
- result is changed
- result.image_names | length == 1
- result.image_names[0] == image_names[0]
- result.images | length == 1
- result.image_names | length in [1, 2]
- (result.image_names | sort) in [[image_names[0]], [image_names[0], image_ids[0]] | sort]
- result.images | length in [1, 2]
- result.images[0].Id == image_ids[0]
- result.images[1].Id | default(image_ids[0]) == image_ids[0]
# Single image by ID
@@ -163,11 +186,17 @@
name: "{{ item }}"
loop: "{{ all_images }}"
- name: Show all images
ansible.builtin.command: docker image ls
- name: Load all images (single image by ID)
docker_image_load:
path: "{{ remote_tmp_dir }}/archive-5.tar"
register: result
- name: Show all images
ansible.builtin.command: docker image ls
- name: Print loaded image names
debug:
var: result.image_names
@@ -197,11 +226,17 @@
name: "{{ item }}"
loop: "{{ all_images }}"
- name: Show all images
ansible.builtin.command: docker image ls
- name: Load all images (names)
docker_image_load:
path: "{{ remote_tmp_dir }}/archive-1.tar"
register: result
- name: Show all images
ansible.builtin.command: docker image ls
- name: Print loaded image names
debug:
var: result.image_names
@@ -142,6 +142,8 @@
- present_3_check.actions[0] == ('Pulled image ' ~ image_name)
- present_3_check.diff.before.id == present_1.diff.after.id
- present_3_check.diff.after.id == 'unknown'
- ansible.builtin.assert:
that:
- present_3 is changed
- present_3.actions | length == 1
- present_3.actions[0] == ('Pulled image ' ~ image_name)
@@ -166,6 +168,11 @@
- present_5.actions[0] == ('Pulled image ' ~ image_name)
- present_5.diff.before.id == present_3.diff.after.id
- present_5.diff.after.id == present_1.diff.after.id
when: docker_cli_version is version("29.0.0", "<")
# From Docker 29 on, Docker won't pull images for other architectures
# if there are better matching ones. The above tests assume it will
# just do what it is told, and thus fail from 29.0.0 on.
# https://github.com/ansible-collections/community.docker/pull/1199
always:
- name: cleanup
@@ -7,11 +7,9 @@
block:
- name: Make sure images are not there
docker_image_remove:
name: "{{ item }}"
name: "sha256:{{ item }}"
force: true
loop:
- "sha256:{{ docker_test_image_digest_v1_image_id }}"
- "sha256:{{ docker_test_image_digest_v2_image_id }}"
loop: "{{ docker_test_image_digest_v1_image_ids + docker_test_image_digest_v2_image_ids }}"
- name: Pull image 1
docker_image_pull:
@@ -82,8 +80,6 @@
always:
- name: cleanup
docker_image_remove:
name: "{{ item }}"
name: "sha256:{{ item }}"
force: true
loop:
- "sha256:{{ docker_test_image_digest_v1_image_id }}"
- "sha256:{{ docker_test_image_digest_v2_image_id }}"
loop: "{{ docker_test_image_digest_v1_image_ids + docker_test_image_digest_v2_image_ids }}"
@@ -18,7 +18,7 @@
- name: Push image ID (must fail)
docker_image_push:
name: "sha256:{{ docker_test_image_digest_v1_image_id }}"
name: "sha256:{{ docker_test_image_digest_v1_image_ids[0] }}"
register: fail_2
ignore_errors: true
@@ -80,4 +80,6 @@
that:
- push_4 is failed
- >-
push_4.msg == ('Error pushing image ' ~ image_name_base2 ~ ':' ~ image_tag ~ ': no basic auth credentials')
push_4.msg.startswith('Error pushing image ' ~ image_name_base2 ~ ':' ~ image_tag ~ ': ')
- >-
push_4.msg.endswith(': no basic auth credentials')
@@ -8,15 +8,16 @@
# and should not be used as examples of how to write Ansible roles #
####################################################################
- block:
- vars:
image: "{{ docker_test_image_hello_world }}"
image_ids: "{{ docker_test_image_hello_world_image_ids }}"
block:
- name: Pick image prefix
set_fact:
iname_prefix: "{{ 'ansible-docker-test-%0x' % ((2**32) | random) }}"
- name: Define image names
set_fact:
image: "{{ docker_test_image_hello_world }}"
image_id: "{{ docker_test_image_hello_world_image_id }}"
image_names:
- "{{ iname_prefix }}-tagged-1:latest"
- "{{ iname_prefix }}-tagged-1:foo"
@@ -24,8 +25,9 @@
- name: Remove image complete
docker_image_remove:
name: "{{ image_id }}"
name: "{{ item }}"
force: true
loop: "{{ image_ids }}"
- name: Remove tagged images
docker_image_remove:
@@ -102,10 +104,11 @@
- remove_2 is changed
- remove_2.diff.before.id == pulled_image.image.Id
- remove_2.diff.before.tags | length == 4
- remove_2.diff.before.digests | length == 1
# With Docker 29, there are now two digests in before and after:
- remove_2.diff.before.digests | length in [1, 2]
- remove_2.diff.after.id == pulled_image.image.Id
- remove_2.diff.after.tags | length == 3
- remove_2.diff.after.digests | length == 1
- remove_2.diff.after.digests | length in [1, 2]
- remove_2.deleted | length == 0
- remove_2.untagged | length == 1
- remove_2.untagged[0] == (iname_prefix ~ '-tagged-1:latest')
@@ -174,10 +177,11 @@
- remove_4 is changed
- remove_4.diff.before.id == pulled_image.image.Id
- remove_4.diff.before.tags | length == 3
- remove_4.diff.before.digests | length == 1
# With Docker 29, there are now two digests in before and after:
- remove_4.diff.before.digests | length in [1, 2]
- remove_4.diff.after.id == pulled_image.image.Id
- remove_4.diff.after.tags | length == 2
- remove_4.diff.after.digests | length == 1
- remove_4.diff.after.digests | length in [1, 2]
- remove_4.deleted | length == 0
- remove_4.untagged | length == 1
- remove_4.untagged[0] == (iname_prefix ~ '-tagged-1:foo')
@@ -245,16 +249,22 @@
- remove_6 is changed
- remove_6.diff.before.id == pulled_image.image.Id
- remove_6.diff.before.tags | length == 2
- remove_6.diff.before.digests | length == 1
# With Docker 29, there are now two digests in before and after:
- remove_6.diff.before.digests | length in [1, 2]
- remove_6.diff.after.exists is false
- remove_6.deleted | length > 1
- remove_6.deleted | length >= 1
- pulled_image.image.Id in remove_6.deleted
- remove_6.untagged | length == 3
- remove_6.untagged | length in [2, 3]
- (iname_prefix ~ '-tagged-1:bar') in remove_6.untagged
- image in remove_6.untagged
- remove_6_check.deleted | length == 1
- remove_6_check.deleted[0] == pulled_image.image.Id
- remove_6_check.untagged == remove_6.untagged
# The following is only true for Docker < 29...
# We use the CLI version as a proxy...
- >-
remove_6_check.untagged == remove_6.untagged
or
docker_cli_version is version("29.0.0", ">=")
- info_5.images | length == 0
- name: Remove image ID (force, idempotent, check mode)
@@ -4,12 +4,18 @@
# SPDX-License-Identifier: GPL-3.0-or-later
docker_test_image_digest_v1: e004c2cc521c95383aebb1fb5893719aa7a8eae2e7a71f316a4410784edb00a9
docker_test_image_digest_v1_image_id: 758ec7f3a1ee85f8f08399b55641bfb13e8c1109287ddc5e22b68c3d653152ee
docker_test_image_digest_v1_image_ids:
- 758ec7f3a1ee85f8f08399b55641bfb13e8c1109287ddc5e22b68c3d653152ee # Docker 28 and before
- e004c2cc521c95383aebb1fb5893719aa7a8eae2e7a71f316a4410784edb00a9 # Docker 29
docker_test_image_digest_v2: ee44b399df993016003bf5466bd3eeb221305e9d0fa831606bc7902d149c775b
docker_test_image_digest_v2_image_id: dc3bacd8b5ea796cea5d6070c8f145df9076f26a6bc1c8981fd5b176d37de843
docker_test_image_digest_v2_image_ids:
- dc3bacd8b5ea796cea5d6070c8f145df9076f26a6bc1c8981fd5b176d37de843 # Docker 28 and before
- ee44b399df993016003bf5466bd3eeb221305e9d0fa831606bc7902d149c775b # Docker 29
docker_test_image_digest_base: quay.io/ansible/docker-test-containers
docker_test_image_hello_world: quay.io/ansible/docker-test-containers:hello-world
docker_test_image_hello_world_image_id: sha256:bf756fb1ae65adf866bd8c456593cd24beb6a0a061dedf42b26a993176745f6b
docker_test_image_hello_world_image_ids:
- sha256:bf756fb1ae65adf866bd8c456593cd24beb6a0a061dedf42b26a993176745f6b # Docker 28 and before
- sha256:90659bf80b44ce6be8234e6ff90a1ac34acbeb826903b02cfa0da11c82cbc042 # Docker 29
docker_test_image_hello_world_base: quay.io/ansible/docker-test-containers
docker_test_image_busybox: quay.io/ansible/docker-test-containers:busybox
docker_test_image_alpine: quay.io/ansible/docker-test-containers:alpine3.8
@@ -102,7 +102,17 @@
# This host/port combination cannot be used if the tests are running inside a docker container.
docker_registry_frontend_address: localhost:{{ nginx_container.container.NetworkSettings.Ports['5000/tcp'].0.HostPort }}
# The following host/port combination can be used from inside the docker container.
docker_registry_frontend_address_internal: "{{ nginx_container.container.NetworkSettings.Networks[current_container_network_ip].IPAddress if current_container_network_ip else nginx_container.container.NetworkSettings.IPAddress }}:5000"
docker_registry_frontend_address_internal: >-
{{
nginx_container.container.NetworkSettings.Networks[current_container_network_ip].IPAddress
if current_container_network_ip else
(
nginx_container.container.NetworkSettings.IPAddress
| default(nginx_container.container.NetworkSettings.Networks['bridge'].IPAddress)
)
}}:5000
# Since Docker 29, nginx_container.container.NetworkSettings.IPAddress no longer exists.
# Use the bridge network's IP address instead...
- name: Wait for registry frontend
uri:
@@ -27,7 +27,7 @@
- name: Install cryptography (Darwin, and potentially upgrade for other OSes)
become: true
pip:
name: cryptography>=1.3.0
name: cryptography>=3.3.0
extra_args: "-c {{ remote_constraints }}"
- name: Register cryptography version