mirror of
https://github.com/containers/ansible-podman-collections.git
synced 2026-03-22 02:29:08 +00:00
Add --platform option to podman_image
Fix #1003 Signed-off-by: Sagi Shnaidman <sshnaidm@redhat.com>
This commit is contained in:
parent
cc98f4430c
commit
7c743f70aa
4 changed files with 77 additions and 9 deletions
|
|
@ -136,9 +136,9 @@ class PodmanImageBuilder:
|
||||||
self.executable = executable
|
self.executable = executable
|
||||||
self.auth_config = auth_config or {}
|
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."""
|
"""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
|
# Handle inline container file
|
||||||
temp_file_path = None
|
temp_file_path = None
|
||||||
|
|
@ -162,13 +162,17 @@ class PodmanImageBuilder:
|
||||||
if temp_file_path and os.path.exists(temp_file_path):
|
if temp_file_path and os.path.exists(temp_file_path):
|
||||||
os.remove(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."""
|
"""Construct build command arguments."""
|
||||||
args = ["build", "-t", image_name]
|
args = ["build", "-t", image_name]
|
||||||
|
|
||||||
# Add authentication
|
# Add authentication
|
||||||
self._add_auth_args(args)
|
self._add_auth_args(args)
|
||||||
|
|
||||||
|
# Add platform for cross-platform builds
|
||||||
|
if platform:
|
||||||
|
args.extend(["--platform", platform])
|
||||||
|
|
||||||
# Add build-specific arguments
|
# Add build-specific arguments
|
||||||
if build_config.get("force_rm"):
|
if build_config.get("force_rm"):
|
||||||
args.append("--force-rm")
|
args.append("--force-rm")
|
||||||
|
|
@ -268,11 +272,13 @@ class PodmanImagePuller:
|
||||||
self.executable = executable
|
self.executable = executable
|
||||||
self.auth_config = auth_config or {}
|
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."""
|
"""Pull an image from a registry."""
|
||||||
args = ["pull", image_name]
|
args = ["pull", image_name]
|
||||||
|
|
||||||
if arch:
|
if platform:
|
||||||
|
args.extend(["--platform", platform])
|
||||||
|
elif arch:
|
||||||
args.extend(["--arch", arch])
|
args.extend(["--arch", arch])
|
||||||
|
|
||||||
self._add_auth_args(args)
|
self._add_auth_args(args)
|
||||||
|
|
@ -533,10 +539,28 @@ class PodmanImageManager:
|
||||||
|
|
||||||
# Check architecture if specified
|
# Check architecture if specified
|
||||||
arch = self.params.get("arch")
|
arch = self.params.get("arch")
|
||||||
|
platform = self.params.get("platform")
|
||||||
if arch:
|
if arch:
|
||||||
inspect_data = self.inspector.inspect_image(image_name)
|
inspect_data = self.inspector.inspect_image(image_name)
|
||||||
if inspect_data and inspect_data[0].get("Architecture") != arch:
|
if inspect_data and inspect_data[0].get("Architecture") != arch:
|
||||||
return None
|
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
|
return images
|
||||||
|
|
||||||
|
|
@ -618,7 +642,8 @@ class PodmanImageManager:
|
||||||
# Build the image
|
# Build the image
|
||||||
if not self.module.check_mode:
|
if not self.module.check_mode:
|
||||||
image_id, output, podman_command = self.builder.build_image(
|
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["stdout"] = output
|
||||||
self.results["image"] = self.inspector.inspect_image(image_id)
|
self.results["image"] = self.inspector.inspect_image(image_id)
|
||||||
|
|
@ -634,7 +659,10 @@ class PodmanImageManager:
|
||||||
|
|
||||||
if not self.module.check_mode:
|
if not self.module.check_mode:
|
||||||
unused, podman_command = self.puller.pull_image(
|
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["image"] = self.inspector.inspect_image(self.repository.full_name)
|
||||||
self.results["podman_actions"].append(podman_command)
|
self.results["podman_actions"].append(podman_command)
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,12 @@ DOCUMENTATION = r"""
|
||||||
description:
|
description:
|
||||||
- CPU architecture for the container image
|
- CPU architecture for the container image
|
||||||
type: str
|
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:
|
name:
|
||||||
description:
|
description:
|
||||||
- Name of the image to pull, push, or delete. It may contain a tag using the format C(image:tag).
|
- 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
|
name: nginx
|
||||||
arch: amd64
|
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
|
- name: Build a container from file inline
|
||||||
containers.podman.podman_image:
|
containers.podman.podman_image:
|
||||||
name: mycustom_image
|
name: mycustom_image
|
||||||
|
|
@ -456,6 +467,7 @@ def main():
|
||||||
argument_spec=dict(
|
argument_spec=dict(
|
||||||
name=dict(type="str", required=True),
|
name=dict(type="str", required=True),
|
||||||
arch=dict(type="str"),
|
arch=dict(type="str"),
|
||||||
|
platform=dict(type="str"),
|
||||||
tag=dict(type="str", default="latest"),
|
tag=dict(type="str", default="latest"),
|
||||||
pull=dict(type="bool", default=True),
|
pull=dict(type="bool", default=True),
|
||||||
pull_extra_args=dict(type="str"),
|
pull_extra_args=dict(type="str"),
|
||||||
|
|
@ -528,6 +540,7 @@ def main():
|
||||||
mutually_exclusive=(
|
mutually_exclusive=(
|
||||||
["auth_file", "username"],
|
["auth_file", "username"],
|
||||||
["auth_file", "password"],
|
["auth_file", "password"],
|
||||||
|
["arch", "platform"],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -380,6 +380,26 @@
|
||||||
- item.Architecture == "arm"
|
- item.Architecture == "arm"
|
||||||
loop: "{{ imageinfo_arch.images }}"
|
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
|
- name: Build Docker image
|
||||||
containers.podman.podman_image:
|
containers.podman.podman_image:
|
||||||
executable: "{{ test_executable | default('podman') }}"
|
executable: "{{ test_executable | default('podman') }}"
|
||||||
|
|
@ -599,9 +619,11 @@
|
||||||
state: absent
|
state: absent
|
||||||
loop:
|
loop:
|
||||||
- docker.io/library/ubuntu
|
- docker.io/library/ubuntu
|
||||||
|
- docker.io/library/alpine
|
||||||
- quay.io/sshnaidm1/alpine-sh
|
- quay.io/sshnaidm1/alpine-sh
|
||||||
- quay.io/coreos/etcd:v3.3.11
|
- quay.io/coreos/etcd:v3.3.11
|
||||||
- quay.io/coreos/etcd:v3.5.19
|
- quay.io/coreos/etcd:v3.5.19
|
||||||
|
- quay.io/coreos/etcd:v3.5.27
|
||||||
- localhost/testimage
|
- localhost/testimage
|
||||||
- localhost/testimage2
|
- localhost/testimage2
|
||||||
- localhost/testimage2:testtag
|
- localhost/testimage2:testtag
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,8 @@ class TestPodmanImageModule:
|
||||||
),
|
),
|
||||||
# Valid authentication parameters
|
# Valid authentication parameters
|
||||||
({"name": "alpine", "username": "testuser", "password": "testpass"}, True),
|
({"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):
|
def test_module_parameter_validation(self, test_params, expected_valid):
|
||||||
|
|
@ -138,19 +140,22 @@ class TestPodmanImageModule:
|
||||||
mutually_exclusive_combinations = [
|
mutually_exclusive_combinations = [
|
||||||
({"auth_file": "/path/to/auth", "username": "user"}, True),
|
({"auth_file": "/path/to/auth", "username": "user"}, True),
|
||||||
({"auth_file": "/path/to/auth", "password": "pass"}, 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
|
({"username": "user", "password": "pass"}, False), # This should be allowed
|
||||||
({"auth_file": "/path/to/auth"}, 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:
|
for params, should_be_exclusive in mutually_exclusive_combinations:
|
||||||
# This tests the logic of mutual exclusion
|
# This tests the logic of mutual exclusion
|
||||||
has_auth_file = "auth_file" in params
|
has_auth_file = "auth_file" in params
|
||||||
has_credentials = "username" in params or "password" 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:
|
if should_be_exclusive:
|
||||||
assert has_auth_file and has_credentials
|
assert (has_auth_file and has_credentials) or has_arch_and_platform
|
||||||
else:
|
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):
|
def test_required_together_logic(self):
|
||||||
"""Test that username and password are required together."""
|
"""Test that username and password are required together."""
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue