Docker image export platform (#1251)

* docker_image_export: Add 'platform' option

docker_image_export: Add 'platform' option

* docker_image_export: Add 'platform' option

* Update changelogs/fragments/1064-docker-image-export-platform.yml

Co-authored-by: Felix Fontein <felix@fontein.de>

* Update plugins/modules/docker_image_export.py

Co-authored-by: Felix Fontein <felix@fontein.de>

* docker_image_export: Add 'platform' option

* docker_image_export: Add 'platform' option

* docker_image_export: Add 'platform' option

---------

Co-authored-by: Felix Fontein <felix@fontein.de>
This commit is contained in:
spatterlight 2026-03-27 08:16:04 -04:00 committed by GitHub
parent 91b350019a
commit 9c313bb9d0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 79 additions and 3 deletions

View File

@ -0,0 +1,2 @@
minor_changes:
- docker_image_export - adds ``platform`` parameter to allow exporting a specific platform variant from a multi-arch image (https://github.com/ansible-collections/community.docker/issues/1064, https://github.com/ansible-collections/community.docker/pull/1251).

View File

@ -31,9 +31,12 @@ attributes:
details:
- Whether the module is idempotent depends on the storage API used for images,
which determines how the image ID is computed. The idempotency check needs
that the image ID equals the ID stored in archive's C(manifest.json).
the image ID to equal the ID stored in archive's C(manifest.json).
This seemed to have worked fine with the default storage backend up to Docker 28,
but seems to have changed in Docker 29.
- This module is B(not idempotent) when used with multi-architecture images,
regardless of Docker version.
- Full idempotency requires Docker 28 or earlier B(and) a single-architecture image.
options:
names:
@ -61,6 +64,13 @@ options:
- Export the image even if the C(.tar) file already exists and seems to contain the right image.
type: bool
default: false
platform:
description:
- Ask for this specific platform when exporting.
- For example, C(linux/amd64), C(linux/arm64).
- Requires Docker API 1.48 or newer.
type: str
version_added: 5.2.0
requirements:
- "Docker API >= 1.25"
@ -98,6 +108,7 @@ images:
sample: []
"""
import json
import traceback
import typing as t
@ -119,6 +130,9 @@ from ansible_collections.community.docker.plugins.module_utils._image_archive im
api_image_id,
load_archived_image_manifest,
)
from ansible_collections.community.docker.plugins.module_utils._platform import (
_Platform,
)
from ansible_collections.community.docker.plugins.module_utils._util import (
DockerBaseClass,
is_image_name_id,
@ -137,6 +151,7 @@ class ImageExportManager(DockerBaseClass):
self.path = parameters["path"]
self.force = parameters["force"]
self.tag = parameters["tag"]
self.platform = parameters["platform"]
if not is_valid_tag(self.tag, allow_empty=True):
self.fail(f'"{self.tag}" is not a valid docker tag')
@ -198,15 +213,31 @@ class ImageExportManager(DockerBaseClass):
except Exception as exc: # pylint: disable=broad-exception-caught
self.fail(f"Error writing image archive {self.path} - {exc}")
def _platform_param(self) -> str:
platform = _Platform.parse_platform_string(self.platform)
platform_spec: dict[str, str] = {}
if platform.os:
platform_spec["os"] = platform.os
if platform.arch:
platform_spec["architecture"] = platform.arch
if platform.variant:
platform_spec["variant"] = platform.variant
return json.dumps(platform_spec)
def export_images(self) -> None:
image_names = [name["joined"] for name in self.names]
image_names_str = ", ".join(image_names)
if len(image_names) == 1:
self.log(f"Getting archive of image {image_names[0]}")
params: dict[str, t.Any] = {}
if self.platform:
params["platform"] = self._platform_param()
try:
chunks = self.client._stream_raw_result(
self.client._get(
self.client._url("/images/{0}/get", image_names[0]), stream=True
self.client._url("/images/{0}/get", image_names[0]),
stream=True,
params=params,
),
chunk_size=DEFAULT_DATA_CHUNK_SIZE,
decode=False,
@ -215,12 +246,15 @@ class ImageExportManager(DockerBaseClass):
self.fail(f"Error getting image {image_names[0]} - {exc}")
else:
self.log(f"Getting archive of images {image_names_str}")
params = {"names": image_names}
if self.platform:
params["platform"] = self._platform_param()
try:
chunks = self.client._stream_raw_result(
self.client._get(
self.client._url("/images/get"),
stream=True,
params={"names": image_names},
params=params,
),
chunk_size=DEFAULT_DATA_CHUNK_SIZE,
decode=False,
@ -277,11 +311,17 @@ def main() -> None:
"aliases": ["name"],
},
"tag": {"type": "str", "default": "latest"},
"platform": {"type": "str"},
}
option_minimal_versions = {
"platform": {"docker_api_version": "1.48"},
}
client = AnsibleDockerClient(
argument_spec=argument_spec,
supports_check_mode=True,
option_minimal_versions=option_minimal_versions,
)
try:

View File

@ -0,0 +1,34 @@
---
# 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
- when: docker_api_version is version('1.48', '>=')
block:
- name: Pull image for platform test
community.docker.docker_image_pull:
name: "{{ docker_test_image_hello_world }}"
platform: linux/amd64
- name: Export image with platform (check mode)
community.docker.docker_image_export:
name: "{{ docker_test_image_hello_world }}"
path: "{{ remote_tmp_dir }}/platform-test.tar"
platform: linux/amd64
register: result_check
check_mode: true
- ansible.builtin.assert:
that:
- result_check is changed
- name: Export image with platform
community.docker.docker_image_export:
name: "{{ docker_test_image_hello_world }}"
path: "{{ remote_tmp_dir }}/platform-test.tar"
platform: linux/amd64
register: result
- ansible.builtin.assert:
that:
- result is changed