Skip to content

Commit

Permalink
Add option for inline Containerfile in podman_image (#781)
Browse files Browse the repository at this point in the history
Signed-off-by: Sagi Shnaidman <[email protected]>
  • Loading branch information
sshnaidm authored Jun 9, 2024
1 parent fb6ebaa commit e60d54f
Show file tree
Hide file tree
Showing 2 changed files with 108 additions and 24 deletions.
88 changes: 64 additions & 24 deletions plugins/modules/podman_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,8 @@
- quadlet
validate_certs:
description:
- Require HTTPS and validate certificates when pulling or pushing. Also used during build if a pull or push is necessary.
- Require HTTPS and validate certificates when pulling or pushing.
Also used during build if a pull or push is necessary.
type: bool
aliases:
- tlsverify
Expand All @@ -98,9 +99,15 @@
- build_args
- buildargs
suboptions:
container_file:
description:
- Content of the Containerfile to use for building the image.
Mutually exclusive with the C(file) option which is path to the existing Containerfile.
type: str
file:
description:
- Path to the Containerfile if it is not in the build context directory.
Mutually exclusive with the C(container_file) option.
type: path
volume:
description:
Expand All @@ -109,7 +116,8 @@
elements: str
annotation:
description:
- Dictionary of key=value pairs to add to the image. Only works with OCI images. Ignored for Docker containers.
- Dictionary of key=value pairs to add to the image. Only works with OCI images.
Ignored for Docker containers.
type: dict
force_rm:
description:
Expand Down Expand Up @@ -152,7 +160,7 @@
type: bool
format:
description:
- Manifest type to use when pushing an image using the 'dir' transport (default is manifest type of source).
- Manifest type to use when pushing an image using the 'dir' transport (default is manifest type of source)
type: str
choices:
- oci
Expand All @@ -172,7 +180,7 @@
- destination
transport:
description:
- Transport to use when pushing in image. If no transport is set, will attempt to push to a remote registry.
- Transport to use when pushing in image. If no transport is set, will attempt to push to a remote registry
type: str
choices:
- dir
Expand Down Expand Up @@ -309,6 +317,15 @@
name: nginx
arch: amd64
- name: Build a container from file inline
containers.podman.podman_image:
name: mycustom_image
state: build
build:
container_file: |-
FROM alpine:latest
CMD echo "Hello, World!"
- name: Create a quadlet file for an image
containers.podman.podman_image:
name: docker.io/library/alpine:latest
Expand Down Expand Up @@ -342,7 +359,7 @@
"/app-entrypoint.sh"
],
"Env": [
"PATH=/opt/bitnami/java/bin:/opt/bitnami/wildfly/bin:/opt/bitnami/nami/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
"PATH=/opt/bitnami/java/bin:/opt/bitnami/wildfly/bin:/opt/bitnami/nami/bin:...",
"IMAGE_OS=debian-9",
"NAMI_VERSION=1.0.0-1",
"GPG_KEY_SERVERS_LIST=ha.pool.sks-keyservers.net",
Expand Down Expand Up @@ -382,10 +399,10 @@
"Digest": "sha256:5a8ab28e314c2222de3feaf6dac94a0436a37fc08979d2722c99d2bef2619a9b",
"GraphDriver": {
"Data": {
"LowerDir": "/var/lib/containers/storage/overlay/142c1beadf1bb09fbd929465ec98c9dca3256638220450efb4214727d0d0680e/diff:/var/lib/containers/s",
"MergedDir": "/var/lib/containers/storage/overlay/9aa10191f5bddb59e28508e721fdeb43505e5b395845fa99723ed787878dbfea/merged",
"UpperDir": "/var/lib/containers/storage/overlay/9aa10191f5bddb59e28508e721fdeb43505e5b395845fa99723ed787878dbfea/diff",
"WorkDir": "/var/lib/containers/storage/overlay/9aa10191f5bddb59e28508e721fdeb43505e5b395845fa99723ed787878dbfea/work"
"LowerDir": "/var/lib/containers/storage/overlay/142c1beadf1bb09fbd929465e..../diff:/var/lib/containers/s",
"MergedDir": "/var/lib/containers/storage/overlay/9aa10191f5bddb59e28508e721fdeb43505e5b395845fa99/merged",
"UpperDir": "/var/lib/containers/storage/overlay/9aa10191f5bddb59e28508e721fdeb43505e5b395845fa99/diff",
"WorkDir": "/var/lib/containers/storage/overlay/9aa10191f5bddb59e28508e721fdeb43505e5b395845fa99/work"
},
"Name": "overlay"
},
Expand Down Expand Up @@ -443,10 +460,12 @@
]
"""

import json
import os
import re
import shlex
import json # noqa: E402
import os # noqa: E402
import re # noqa: E402
import shlex # noqa: E402
import tempfile # noqa: E402
import time # noqa: E402

from ansible.module_utils._text import to_native
from ansible.module_utils.basic import AnsibleModule
Expand Down Expand Up @@ -520,7 +539,7 @@ def _get_id_from_output(self, lines, startswith=None, contains=None, split_on='
if not layer_ids:
layer_ids = lines.splitlines()

return (layer_ids[-1])
return layer_ids[-1]

def present(self):
image = self.find_image()
Expand All @@ -534,12 +553,15 @@ def present(self):
if self.state == 'build' or self.path:
# Build the image
build_file = self.build.get('file') if self.build else None
container_file_txt = self.build.get('container_file') if self.build else None
if build_file and container_file_txt:
self.module.fail_json(msg='Cannot specify both build file and container file content!')
if not self.path and build_file:
self.path = os.path.dirname(build_file)
elif not self.path and not build_file:
elif not self.path and not build_file and not container_file_txt:
self.module.fail_json(msg='Path to build context or file is required when building an image')
self.results['actions'].append('Built image {image_name} from {path}'.format(
image_name=self.image_name, path=self.path))
image_name=self.image_name, path=self.path or 'default context'))
if not self.module.check_mode:
self.results['image'], self.results['stdout'] = self.build_image()
image = self.results['image']
Expand Down Expand Up @@ -669,10 +691,12 @@ def pull_image(self, image_name=None):
rc, out, err = self._run(args, ignore_errors=True)
if rc != 0:
if not self.pull:
self.module.fail_json(msg='Failed to find image {image_name} locally, image pull set to {pull_bool}'.format(
pull_bool=self.pull, image_name=image_name))
self.module.fail_json(
msg='Failed to find image {image_name} locally, image pull set to {pull_bool}'.format(
pull_bool=self.pull, image_name=image_name))
else:
self.module.fail_json(msg='Failed to pull image {image_name}'.format(image_name=image_name))
self.module.fail_json(
msg='Failed to pull image {image_name}'.format(image_name=image_name))
return self.inspect_image(out.strip())

def build_image(self):
Expand Down Expand Up @@ -709,6 +733,17 @@ def build_image(self):
containerfile = self.build.get('file')
if containerfile:
args.extend(['--file', containerfile])
container_file_txt = self.build.get('container_file')
if container_file_txt:
# create a temporarly file with the content of the Containerfile
if self.path:
container_file_path = os.path.join(self.path, 'Containerfile.generated_by_ansible_%s' % time.time())
else:
container_file_path = os.path.join(
tempfile.gettempdir(), 'Containerfile.generated_by_ansible_%s' % time.time())
with open(container_file_path, 'w') as f:
f.write(container_file_txt)
args.extend(['--file', container_file_path])

volume = self.build.get('volume')
if volume:
Expand All @@ -729,13 +764,16 @@ def build_image(self):
target = self.build.get('target')
if target:
args.extend(['--target', target])

args.append(self.path)
if self.path:
args.append(self.path)

rc, out, err = self._run(args, ignore_errors=True)
if rc != 0:
self.module.fail_json(msg="Failed to build image {image}: {out} {err}".format(image=self.image_name, out=out, err=err))

self.module.fail_json(msg="Failed to build image {image}: {out} {err}".format(
image=self.image_name, out=out, err=err))
# remove the temporary file if it was created
if container_file_txt:
os.remove(container_file_path)
last_id = self._get_id_from_output(out, startswith='-->')
return self.inspect_image(last_id), out + err

Expand Down Expand Up @@ -831,7 +869,8 @@ def remove_image(self, image_name=None):
args.append('--force')
rc, out, err = self._run(args, ignore_errors=True)
if rc != 0:
self.module.fail_json(msg='Failed to remove image {image_name}. {err}'.format(image_name=image_name, err=err))
self.module.fail_json(msg='Failed to remove image {image_name}. {err}'.format(
image_name=image_name, err=err))
return out

def remove_image_id(self, image_id=None):
Expand Down Expand Up @@ -887,6 +926,7 @@ def main():
annotation=dict(type='dict'),
force_rm=dict(type='bool', default=False),
file=dict(type='path'),
container_file=dict(type='str'),
format=dict(
type='str',
choices=['oci', 'docker'],
Expand Down
44 changes: 44 additions & 0 deletions tests/integration/targets/podman_image/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,50 @@
path: /var/tmp/build
register: build_image2

- name: Build image from a given Containerfile
containers.podman.podman_image:
executable: "{{ test_executable | default('podman') }}"
name: testimage2:customfile
path: "{{ playbook_dir }}"
build:
container_file: |-
FROM quay.io/coreos/alpine-sh
RUN echo "Hello World" > /tmp/hello.txt
register: build_custom1

- name: Build image from a given Containerfile w/o path
containers.podman.podman_image:
executable: "{{ test_executable | default('podman') }}"
name: testimage2:customfile2
state: build
build:
container_file: |-
FROM quay.io/coreos/alpine-sh
RUN echo "Hello2 World" > /tmp/hello2.txt
force: true
register: build_custom2

- name: Build image from a given Containerfile and file (fail)
containers.podman.podman_image:
executable: "{{ test_executable | default('podman') }}"
name: testimage2:failme
state: build
build:
container_file: |-
FROM quay.io/coreos/alpine-sh
RUN echo "Hello2 World" > /tmp/hello2.txt
file: /var/tmp/build/Dockerfile
force: true
register: fail_custom_image
ignore_errors: true

- name: Check if image was built properly
assert:
that:
- build_custom1 is changed
- build_custom2 is changed
- fail_custom_image is failed

- include_tasks: idem_push.yml

- name: Create a Quadlet for image with filename
Expand Down

0 comments on commit e60d54f

Please sign in to comment.