1
0
Fork 0
mirror of https://github.com/containers/ansible-podman-collections.git synced 2026-03-21 18:19:07 +00:00

Add --platform option to podman_image

Fix #1003
Signed-off-by: Sagi Shnaidman <sshnaidm@redhat.com>
This commit is contained in:
Sagi Shnaidman 2026-02-27 13:51:38 +02:00 committed by Sergey
parent cc98f4430c
commit 88999e2bcf
4 changed files with 77 additions and 9 deletions

View file

@ -136,9 +136,9 @@ class PodmanImageBuilder:
self.executable = executable
self.auth_config = auth_config or {}
def build_image(self, image_name, build_config, path=None, containerfile_hash=None):
def build_image(self, image_name, build_config, path=None, containerfile_hash=None, platform=None):
"""Build an image with the given configuration."""
args = self._construct_build_args(image_name, build_config, path, containerfile_hash)
args = self._construct_build_args(image_name, build_config, path, containerfile_hash, platform)
# Handle inline container file
temp_file_path = None
@ -162,13 +162,17 @@ class PodmanImageBuilder:
if temp_file_path and os.path.exists(temp_file_path):
os.remove(temp_file_path)
def _construct_build_args(self, image_name, build_config, path, containerfile_hash):
def _construct_build_args(self, image_name, build_config, path, containerfile_hash, platform=None):
"""Construct build command arguments."""
args = ["build", "-t", image_name]
# Add authentication
self._add_auth_args(args)
# Add platform for cross-platform builds
if platform:
args.extend(["--platform", platform])
# Add build-specific arguments
if build_config.get("force_rm"):
args.append("--force-rm")
@ -268,11 +272,13 @@ class PodmanImagePuller:
self.executable = executable
self.auth_config = auth_config or {}
def pull_image(self, image_name, arch=None, pull_extra_args=None):
def pull_image(self, image_name, arch=None, platform=None, pull_extra_args=None):
"""Pull an image from a registry."""
args = ["pull", image_name]
if arch:
if platform:
args.extend(["--platform", platform])
elif arch:
args.extend(["--arch", arch])
self._add_auth_args(args)
@ -533,10 +539,28 @@ class PodmanImageManager:
# Check architecture if specified
arch = self.params.get("arch")
platform = self.params.get("platform")
if arch:
inspect_data = self.inspector.inspect_image(image_name)
if inspect_data and inspect_data[0].get("Architecture") != arch:
return None
elif platform:
# Platform format: os/arch or os/arch/variant (e.g. linux/amd64)
inspect_data = self.inspector.inspect_image(image_name)
if inspect_data:
platform_parts = platform.split("/")
required_os = platform_parts[0] if len(platform_parts) >= 1 else None
required_arch = platform_parts[1] if len(platform_parts) >= 2 else None
img_os = inspect_data[0].get("Os") or inspect_data[0].get("os")
img_arch = inspect_data[0].get("Architecture") or inspect_data[0].get("architecture")
# Normalize arch for comparison (e.g. x86_64 -> amd64, aarch64 -> arm64)
arch_map = {"x86_64": "amd64", "aarch64": "arm64"}
if img_arch and img_arch in arch_map:
img_arch = arch_map[img_arch]
if required_os and img_os != required_os:
return None
if required_arch and img_arch != required_arch:
return None
return images
@ -618,7 +642,8 @@ class PodmanImageManager:
# Build the image
if not self.module.check_mode:
image_id, output, podman_command = self.builder.build_image(
self.repository.full_name, build_config, path, containerfile_hash
self.repository.full_name, build_config, path, containerfile_hash,
self.params.get("platform")
)
self.results["stdout"] = output
self.results["image"] = self.inspector.inspect_image(image_id)
@ -634,7 +659,10 @@ class PodmanImageManager:
if not self.module.check_mode:
unused, podman_command = self.puller.pull_image(
self.repository.full_name, self.params.get("arch"), self.params.get("pull_extra_args")
self.repository.full_name,
self.params.get("arch"),
self.params.get("platform"),
self.params.get("pull_extra_args"),
)
self.results["image"] = self.inspector.inspect_image(self.repository.full_name)
self.results["podman_actions"].append(podman_command)

View file

@ -19,6 +19,12 @@ DOCUMENTATION = r"""
description:
- CPU architecture for the container image
type: str
platform:
description:
- Platform for the container image (e.g. C(linux/amd64), C(linux/arm64)).
- Specify the platform for selecting the image when pulling or building.
- Mutually exclusive with C(arch).
type: str
name:
description:
- Name of the image to pull, push, or delete. It may contain a tag using the format C(image:tag).
@ -348,6 +354,11 @@ EXAMPLES = r"""
name: nginx
arch: amd64
- name: Pull an image for a specific platform (e.g. x86 on M-series Mac)
containers.podman.podman_image:
name: nginx
platform: linux/amd64
- name: Build a container from file inline
containers.podman.podman_image:
name: mycustom_image
@ -456,6 +467,7 @@ def main():
argument_spec=dict(
name=dict(type="str", required=True),
arch=dict(type="str"),
platform=dict(type="str"),
tag=dict(type="str", default="latest"),
pull=dict(type="bool", default=True),
pull_extra_args=dict(type="str"),
@ -528,6 +540,7 @@ def main():
mutually_exclusive=(
["auth_file", "username"],
["auth_file", "password"],
["arch", "platform"],
),
)

View file

@ -380,6 +380,26 @@
- item.Architecture == "arm"
loop: "{{ imageinfo_arch.images }}"
- name: Pull an image for a specific platform
containers.podman.podman_image:
executable: "{{ test_executable | default('podman') }}"
name: quay.io/coreos/etcd:v3.5.27
platform: linux/amd64
register: pull_platform1
- name: Pull the same image for the same platform
containers.podman.podman_image:
executable: "{{ test_executable | default('podman') }}"
name: quay.io/coreos/etcd:v3.5.27
platform: linux/amd64
register: pull_platform2
- name: Ensure platform pull is idempotent
assert:
that:
- pull_platform1 is changed
- pull_platform2 is not changed
- name: Build Docker image
containers.podman.podman_image:
executable: "{{ test_executable | default('podman') }}"
@ -599,9 +619,11 @@
state: absent
loop:
- docker.io/library/ubuntu
- docker.io/library/alpine
- quay.io/sshnaidm1/alpine-sh
- quay.io/coreos/etcd:v3.3.11
- quay.io/coreos/etcd:v3.5.19
- quay.io/coreos/etcd:v3.5.27
- localhost/testimage
- localhost/testimage2
- localhost/testimage2:testtag

View file

@ -48,6 +48,8 @@ class TestPodmanImageModule:
),
# Valid authentication parameters
({"name": "alpine", "username": "testuser", "password": "testpass"}, True),
# Valid platform parameter (issue #1003)
({"name": "alpine", "platform": "linux/amd64"}, True),
],
)
def test_module_parameter_validation(self, test_params, expected_valid):
@ -138,19 +140,22 @@ class TestPodmanImageModule:
mutually_exclusive_combinations = [
({"auth_file": "/path/to/auth", "username": "user"}, True),
({"auth_file": "/path/to/auth", "password": "pass"}, True),
({"arch": "amd64", "platform": "linux/amd64"}, True), # arch and platform
({"username": "user", "password": "pass"}, False), # This should be allowed
({"auth_file": "/path/to/auth"}, False), # This should be allowed
({"platform": "linux/amd64"}, False), # platform alone is allowed
]
for params, should_be_exclusive in mutually_exclusive_combinations:
# This tests the logic of mutual exclusion
has_auth_file = "auth_file" in params
has_credentials = "username" in params or "password" in params
has_arch_and_platform = "arch" in params and "platform" in params
if should_be_exclusive:
assert has_auth_file and has_credentials
assert (has_auth_file and has_credentials) or has_arch_and_platform
else:
assert not (has_auth_file and has_credentials) or not has_auth_file
assert not (has_auth_file and has_credentials) and not has_arch_and_platform
def test_required_together_logic(self):
"""Test that username and password are required together."""