Skip to content

Commit ee52d9d

Browse files
authored
Add podman image scp option (#970)
* Add podman image scp option Fix #536 --------- Signed-off-by: Sagi Shnaidman <sshnaidm@redhat.com>
1 parent c2530a6 commit ee52d9d

File tree

6 files changed

+226
-1
lines changed

6 files changed

+226
-1
lines changed

.github/workflows/podman_image.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,4 @@ jobs:
3636
with:
3737
module_name: 'podman_image'
3838
display_name: 'Podman image'
39+
extra_collections: 'ansible.posix'

.github/workflows/reusable-module-test.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ on:
2727
required: false
2828
type: string
2929
default: '["git+https://github.com/ansible/ansible.git@stable-2.18", "git+https://github.com/ansible/ansible.git@devel"]'
30+
extra_collections:
31+
description: 'Space-separated list of extra Ansible collections to install before running tests (e.g., "ansible.posix community.general")'
32+
required: false
33+
type: string
34+
default: ''
3035

3136
jobs:
3237
test_module:
@@ -78,6 +83,12 @@ jobs:
7883
~/.local/bin/ansible-galaxy collection build --output-path /tmp/just_new_collection --force
7984
~/.local/bin/ansible-galaxy collection install -vvv --force /tmp/just_new_collection/*.tar.gz
8085
86+
- name: Install extra Ansible collections (optional)
87+
if: ${{ inputs.extra_collections != '' }}
88+
run: |
89+
echo "Installing extra collections: ${{ inputs.extra_collections }}"
90+
~/.local/bin/ansible-galaxy collection install -vvv --force ${{ inputs.extra_collections }}
91+
8192
- name: Run collection tests for ${{ inputs.module_name }}
8293
run: |
8394
export PATH=~/.local/bin:$PATH

plugins/module_utils/podman/podman_image_lib.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,44 @@ def __init__(self, module, executable, auth_config=None):
318318

319319
def push_image(self, image_name, push_config):
320320
"""Push an image to a registry."""
321+
transport = (push_config or {}).get("transport")
322+
323+
# Special handling for scp transport which uses 'podman image scp'
324+
if transport == "scp":
325+
args = []
326+
327+
# Allow passing global --ssh options to podman
328+
ssh_opts = push_config.get("ssh") if push_config else None
329+
if ssh_opts:
330+
args.extend(["--ssh", ssh_opts])
331+
332+
args.extend(["image", "scp"])
333+
334+
# Extra args (e.g., --quiet) if provided
335+
if push_config.get("extra_args"):
336+
args.extend(shlex.split(push_config["extra_args"]))
337+
338+
# Source image (local)
339+
args.append(image_name)
340+
341+
# Destination host spec
342+
dest = push_config.get("dest")
343+
if not dest:
344+
self.module.fail_json(msg="When using transport 'scp', push_args.dest must be provided")
345+
346+
# If user did not include '::' in dest, append it to copy into remote storage with same name
347+
dest_spec = dest if "::" in dest else f"{dest}::"
348+
args.append(dest_spec)
349+
350+
action = " ".join(args)
351+
rc, out, err = run_podman_command(self.module, self.executable, args, ignore_errors=True)
352+
if rc != 0:
353+
self.module.fail_json(
354+
msg=f"Failed to scp image {image_name} to {dest}", stdout=out, stderr=err, actions=[action]
355+
)
356+
return out + err, action
357+
358+
# Default push behavior for all other transports
321359
args = ["push"]
322360

323361
self._add_auth_args(args)

plugins/modules/podman_image.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,10 @@
154154
type: dict
155155
default: {}
156156
suboptions:
157+
ssh:
158+
description:
159+
- SSH options to use when pushing images with SCP transport.
160+
type: str
157161
compress:
158162
description:
159163
- Compress tarball image layers when pushing to a directory using the 'dir' transport.
@@ -184,11 +188,12 @@
184188
type: str
185189
choices:
186190
- dir
187-
- docker
188191
- docker-archive
189192
- docker-daemon
190193
- oci-archive
191194
- ostree
195+
- docker
196+
- scp
192197
extra_args:
193198
description:
194199
- Extra args to pass to push, if executed. Does not idempotently check for new push args.
@@ -329,6 +334,15 @@
329334
tag: 3
330335
dest: docker.io/acme
331336
337+
- name: Push image to a remote host via scp transport
338+
containers.podman.podman_image:
339+
name: testimage
340+
pull: false
341+
push: true
342+
push_args:
343+
dest: user@server
344+
transport: scp
345+
332346
- name: Pull an image for a specific CPU architecture
333347
containers.podman.podman_image:
334348
name: nginx
@@ -484,6 +498,7 @@ def main():
484498
type="dict",
485499
default={},
486500
options=dict(
501+
ssh=dict(type="str"),
487502
compress=dict(type="bool"),
488503
format=dict(type="str", choices=["oci", "v2s1", "v2s2"]),
489504
remove_signatures=dict(type="bool"),
@@ -502,6 +517,7 @@ def main():
502517
"oci-archive",
503518
"ostree",
504519
"docker",
520+
"scp",
505521
],
506522
),
507523
),

tests/integration/targets/podman_image/tasks/additional_tests.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,10 @@
186186
- extra_args_build is changed
187187
- "'Built image test-extra-args:latest from' in extra_args_build.actions[0]"
188188

189+
# SCP transport tests
190+
- name: Include scp transport tests
191+
include_tasks: scp.yml
192+
189193
# Test push functionality with different transports
190194
- name: Test push to directory transport
191195
containers.podman.podman_image:
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
- name: Validate scp transport behavior in podman_image
2+
block:
3+
- name: Fail when scp transport is used without destination
4+
containers.podman.podman_image:
5+
executable: "{{ test_executable | default('podman') }}"
6+
name: testimage
7+
pull: false
8+
push: true
9+
push_args:
10+
transport: scp
11+
register: scp_missing_dest
12+
ignore_errors: true
13+
14+
- name: Ensure scp without dest fails with clear message
15+
assert:
16+
that:
17+
- scp_missing_dest is failed
18+
- "'push_args.dest must be provided' in scp_missing_dest.msg"
19+
20+
21+
- name: Build a local image to test scp transport idempotence
22+
containers.podman.podman_image:
23+
executable: "{{ test_executable | default('podman') }}"
24+
name: testimage_scp
25+
path: /var/tmp/build
26+
register: built_local
27+
28+
- name: Try to scp push to a fake remote (should fail on CI env without remote)
29+
containers.podman.podman_image:
30+
executable: "{{ test_executable | default('podman') }}"
31+
name: testimage_scp
32+
pull: false
33+
push: true
34+
push_args:
35+
dest: user@server
36+
transport: scp
37+
register: scp_push_fake
38+
ignore_errors: true
39+
40+
- name: Ensure scp push to fake remote fails but reports action
41+
assert:
42+
that:
43+
- built_local is changed
44+
- scp_push_fake is failed
45+
- scp_push_fake.actions is defined
46+
47+
- name: Prepare SSH access to localhost for scp tests
48+
block:
49+
50+
- name: Ensure SSH keys exist
51+
ansible.builtin.shell: >-
52+
ssh-keygen -b 2048 -t rsa -f {{ lookup('env','HOME') }}/.ssh/id_rsa -N "" || true
53+
args:
54+
creates: "{{ lookup('env','HOME') }}/.ssh/id_rsa"
55+
56+
- name: Get public key for user
57+
ansible.builtin.command: >-
58+
cat {{ lookup('env','HOME') }}/.ssh/id_rsa.pub
59+
register: public_key
60+
61+
- name: Authorize our public key for localhost for user
62+
ansible.posix.authorized_key:
63+
user: "{{ lookup('env','USER') }}"
64+
state: present
65+
key: "{{ public_key.stdout }}"
66+
67+
- name: Authorize our public key for localhost for root user
68+
become: true
69+
ansible.posix.authorized_key:
70+
user: root
71+
state: present
72+
key: "{{ public_key.stdout }}"
73+
74+
- name: Start SSH service (Ubuntu uses 'ssh')
75+
ansible.builtin.systemd_service:
76+
name: ssh
77+
state: started
78+
become: true
79+
ignore_errors: true
80+
81+
- name: Start SSH service (fallback to 'sshd')
82+
ansible.builtin.systemd_service:
83+
name: sshd
84+
state: started
85+
become: true
86+
ignore_errors: true
87+
88+
- name: Verify we can SSH to localhost non-interactively
89+
ansible.builtin.command: >-
90+
ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null {{ lookup('env','USER') }}@localhost true
91+
92+
- name: Build a local image for scp to localhost
93+
containers.podman.podman_image:
94+
executable: "{{ test_executable | default('podman') }}"
95+
name: testimage_scp_local
96+
path: /var/tmp/build
97+
register: built_localhost
98+
99+
- name: Add system connection for Podman < 5
100+
ansible.builtin.command: podman system connection add local --identity {{ lookup('env','HOME') }}/.ssh/id_rsa {{ lookup('env','USER') }}@127.0.0.1
101+
102+
- name: Add system connection for root user for Podman < 5
103+
ansible.builtin.command: podman system connection add rootlocal --identity {{ lookup('env','HOME') }}/.ssh/id_rsa root@127.0.0.1
104+
105+
- name: Push image to localhost via scp transport
106+
containers.podman.podman_image:
107+
executable: "{{ test_executable | default('podman') }}"
108+
name: testimage_scp_local
109+
pull: false
110+
push: true
111+
push_args:
112+
dest: "local::newimage"
113+
transport: scp
114+
register: scp_localhost_push
115+
116+
- name: Validate scp localhost push executed
117+
assert:
118+
that:
119+
- built_localhost is changed
120+
- scp_localhost_push is changed
121+
- scp_localhost_push.actions is defined
122+
- scp_localhost_push.podman_actions is defined
123+
- scp_localhost_push.actions | select('search', 'image scp') | list | length > 0
124+
125+
- name: Push image to localhost via scp transport root user
126+
containers.podman.podman_image:
127+
executable: "{{ test_executable | default('podman') }}"
128+
name: testimage_scp_local
129+
pull: false
130+
push: true
131+
push_args:
132+
dest: "rootlocal"
133+
transport: scp
134+
register: scp_localhost_push
135+
136+
- name: Validate scp localhost push executed
137+
assert:
138+
that:
139+
- built_localhost is changed
140+
- scp_localhost_push is changed
141+
- scp_localhost_push.actions is defined
142+
- scp_localhost_push.podman_actions is defined
143+
- scp_localhost_push.actions | select('search', 'image scp') | list | length > 0
144+
145+
- name: Ensure image is available for root user
146+
become: true
147+
ansible.builtin.command: >-
148+
podman images --format '{{ '{{.Repository}}:{{.Tag}}' }}' testimage_scp_local
149+
register: scp_localhost_root_check
150+
151+
- name: Validate image is available for root user
152+
assert:
153+
that:
154+
- "'testimage_scp_local:latest' in scp_localhost_root_check.stdout"
155+
- scp_localhost_root_check.stderr == ''

0 commit comments

Comments
 (0)