1
0
Fork 0
mirror of https://github.com/containers/ansible-podman-collections.git synced 2026-02-04 07:11:49 +00:00

Add podman image scp option

Fix #536
Signed-off-by: Sagi Shnaidman <sshnaidm@redhat.com>
This commit is contained in:
Sagi Shnaidman 2025-08-18 23:57:35 +03:00
parent 9cffa671c8
commit e352a5d154
6 changed files with 231 additions and 1 deletions

View file

@ -36,3 +36,4 @@ jobs:
with:
module_name: 'podman_image'
display_name: 'Podman image'
extra_collections: 'ansible.posix'

View file

@ -27,6 +27,11 @@ on:
required: false
type: string
default: '["git+https://github.com/ansible/ansible.git@stable-2.18", "git+https://github.com/ansible/ansible.git@devel"]'
extra_collections:
description: 'Space-separated list of extra Ansible collections to install before running tests (e.g., "ansible.posix community.general")'
required: false
type: string
default: ''
jobs:
test_module:
@ -78,6 +83,12 @@ jobs:
~/.local/bin/ansible-galaxy collection build --output-path /tmp/just_new_collection --force
~/.local/bin/ansible-galaxy collection install -vvv --force /tmp/just_new_collection/*.tar.gz
- name: Install extra Ansible collections (optional)
if: ${{ inputs.extra_collections != '' }}
run: |
echo "Installing extra collections: ${{ inputs.extra_collections }}"
~/.local/bin/ansible-galaxy collection install -vvv --force ${{ inputs.extra_collections }}
- name: Run collection tests for ${{ inputs.module_name }}
run: |
export PATH=~/.local/bin:$PATH

View file

@ -318,6 +318,44 @@ class PodmanImagePusher:
def push_image(self, image_name, push_config):
"""Push an image to a registry."""
transport = (push_config or {}).get("transport")
# Special handling for scp transport which uses 'podman image scp'
if transport == "scp":
args = []
# Allow passing global --ssh options to podman
ssh_opts = push_config.get("ssh") if push_config else None
if ssh_opts:
args.extend(["--ssh", ssh_opts])
args.extend(["image", "scp"])
# Extra args (e.g., --quiet) if provided
if push_config.get("extra_args"):
args.extend(shlex.split(push_config["extra_args"]))
# Source image (local)
args.append(image_name)
# Destination host spec
dest = push_config.get("dest")
if not dest:
self.module.fail_json(msg="When using transport 'scp', push_args.dest must be provided")
# If user did not include '::' in dest, append it to copy into remote storage with same name
dest_spec = dest if "::" in dest else f"{dest}::"
args.append(dest_spec)
action = " ".join(args)
rc, out, err = run_podman_command(self.module, self.executable, args, ignore_errors=True)
if rc != 0:
self.module.fail_json(
msg=f"Failed to scp image {image_name} to {dest}", stdout=out, stderr=err, actions=[action]
)
return out + err, action
# Default push behavior for all other transports
args = ["push"]
self._add_auth_args(args)

View file

@ -154,6 +154,10 @@ DOCUMENTATION = r"""
type: dict
default: {}
suboptions:
ssh:
description:
- SSH options to use when pushing images with SCP transport.
type: str
compress:
description:
- Compress tarball image layers when pushing to a directory using the 'dir' transport.
@ -184,11 +188,12 @@ DOCUMENTATION = r"""
type: str
choices:
- dir
- docker
- docker-archive
- docker-daemon
- oci-archive
- ostree
- docker
- scp
extra_args:
description:
- Extra args to pass to push, if executed. Does not idempotently check for new push args.
@ -329,6 +334,15 @@ EXAMPLES = r"""
tag: 3
dest: docker.io/acme
- name: Push image to a remote host via scp transport
containers.podman.podman_image:
name: testimage
pull: false
push: true
push_args:
dest: user@server
transport: scp
- name: Pull an image for a specific CPU architecture
containers.podman.podman_image:
name: nginx
@ -484,6 +498,7 @@ def main():
type="dict",
default={},
options=dict(
ssh=dict(type="str"),
compress=dict(type="bool"),
format=dict(type="str", choices=["oci", "v2s1", "v2s2"]),
remove_signatures=dict(type="bool"),
@ -502,6 +517,7 @@ def main():
"oci-archive",
"ostree",
"docker",
"scp",
],
),
),

View file

@ -186,6 +186,10 @@
- extra_args_build is changed
- "'Built image test-extra-args:latest from' in extra_args_build.actions[0]"
# SCP transport tests (environment has no remote, so validate errors and argument checks)
- name: Include scp transport negative tests
include_tasks: scp.yml
# Test push functionality with different transports
- name: Test push to directory transport
containers.podman.podman_image:

View file

@ -0,0 +1,160 @@
- name: Validate scp transport behavior in podman_image
block:
- name: Fail when scp transport is used without destination
containers.podman.podman_image:
executable: "{{ test_executable | default('podman') }}"
name: testimage
pull: false
push: true
push_args:
transport: scp
register: scp_missing_dest
ignore_errors: true
- name: Ensure scp without dest fails with clear message
assert:
that:
- scp_missing_dest is failed
- "'push_args.dest must be provided' in scp_missing_dest.msg"
- name: Build a local image to test scp transport idempotence
containers.podman.podman_image:
executable: "{{ test_executable | default('podman') }}"
name: testimage_scp
path: /var/tmp/build
register: built_local
- name: Try to scp push to a fake remote (should fail on CI env without remote)
containers.podman.podman_image:
executable: "{{ test_executable | default('podman') }}"
name: testimage_scp
pull: false
push: true
push_args:
dest: user@server
transport: scp
register: scp_push_fake
ignore_errors: true
- name: Ensure scp push to fake remote fails but reports action
assert:
that:
- built_local is changed
- scp_push_fake is failed
- scp_push_fake.actions is defined
- name: Prepare SSH access to localhost for scp tests
block:
- name: Ensure ~/.ssh exists
ansible.builtin.file:
path: "{{ lookup('env','HOME') }}/.ssh"
state: directory
mode: '0700'
- name: Generate SSH key if missing
ansible.builtin.command: >-
ssh-keygen -t ed25519 -q -N '' -f {{ lookup('env','HOME') }}/.ssh/id_ed25519
args:
creates: "{{ lookup('env','HOME') }}/.ssh/id_ed25519"
- name: Get public key for user
ansible.builtin.command: >-
cat {{ lookup('env','HOME') }}/.ssh/id_ed25519.pub
register: public_key
- name: Authorize our public key for localhost for user
ansible.posix.authorized_key:
user: "{{ lookup('env','USER') }}"
state: present
key: "{{ public_key.stdout }}"
- name: Authorize our public key for localhost for root user
become: true
ansible.posix.authorized_key:
user: root
state: present
key: "{{ public_key.stdout }}"
- name: Start SSH service (Ubuntu uses 'ssh')
ansible.builtin.systemd_service:
name: ssh
state: started
become: true
ignore_errors: true
- name: Start SSH service (fallback to 'sshd')
ansible.builtin.systemd_service:
name: sshd
state: started
become: true
ignore_errors: true
- name: Verify we can SSH to localhost non-interactively
ansible.builtin.command: >-
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null {{ lookup('env','USER') }}@localhost true
- name: Build a local image for scp to localhost
containers.podman.podman_image:
executable: "{{ test_executable | default('podman') }}"
name: testimage_scp_local
path: /var/tmp/build
register: built_localhost
- name: Add system connection for Podman < 5
ansible.builtin.command: podman system connection add local --identity {{ lookup('env','HOME') }}/.ssh/id_ed25519 ubuntu@127.0.0.1
- name: Add system connection for root user for Podman < 5
ansible.builtin.command: podman system connection add rootlocal --identity {{ lookup('env','HOME') }}/.ssh/id_ed25519 root@127.0.0.1
- name: Push image to localhost via scp transport
containers.podman.podman_image:
executable: "{{ test_executable | default('podman') }}"
name: testimage_scp_local
pull: false
push: true
push_args:
dest: "local::newimage"
transport: scp
register: scp_localhost_push
- name: Validate scp localhost push executed
assert:
that:
- built_localhost is changed
- scp_localhost_push is changed
- scp_localhost_push.actions is defined
- scp_localhost_push.podman_actions is defined
- scp_localhost_push.actions | select('search', 'image scp') | list | length > 0
- name: Push image to localhost via scp transport root user
containers.podman.podman_image:
executable: "{{ test_executable | default('podman') }}"
name: testimage_scp_local
pull: false
push: true
push_args:
dest: "rootlocal"
transport: scp
register: scp_localhost_push
- name: Validate scp localhost push executed
assert:
that:
- built_localhost is changed
- scp_localhost_push is changed
- scp_localhost_push.actions is defined
- scp_localhost_push.podman_actions is defined
- scp_localhost_push.actions | select('search', 'image scp') | list | length > 0
- name: Ensure image is available for root user
become: true
ansible.builtin.command: >-
podman images --format '{{ '{{.Repository}}:{{.Tag}}' }}' testimage_scp_local
register: scp_localhost_root_check
- name: Validate image is available for root user
assert:
that:
- "'testimage_scp_local:latest' in scp_localhost_root_check.stdout"
- scp_localhost_root_check.stderr == ''