mirror of
https://github.com/containers/ansible-podman-collections.git
synced 2026-02-04 07:11:49 +00:00
Trigger a new image build when we detect that the Containerfile has changed. (#811)
* Trigger a new image build when we detect that the Containerfile has changed. Signed-off-by: gw <gw@bob.lol> * Fix return type issues from PR feedback Signed-off-by: gw <gw@bob.lol> --------- Signed-off-by: gw <gw@bob.lol> Co-authored-by: gw <gw@bob.lol>
This commit is contained in:
parent
2adc93ffb1
commit
58edc41196
2 changed files with 124 additions and 3 deletions
|
|
@ -416,6 +416,7 @@ import re # noqa: E402
|
|||
import shlex # noqa: E402
|
||||
import tempfile # noqa: E402
|
||||
import time # noqa: E402
|
||||
import hashlib # noqa: E402
|
||||
|
||||
from ansible.module_utils._text import to_native
|
||||
from ansible.module_utils.basic import AnsibleModule
|
||||
|
|
@ -491,15 +492,102 @@ class PodmanImageManager(object):
|
|||
|
||||
return layer_ids[-1]
|
||||
|
||||
def _find_containerfile_from_context(self):
|
||||
"""
|
||||
Find a Containerfile/Dockerfile path inside a podman build context.
|
||||
Return 'None' if none exist.
|
||||
"""
|
||||
|
||||
containerfile_path = None
|
||||
for filename in [os.path.join(self.path, fname) for fname in ["Containerfile", "Dockerfile"]]:
|
||||
if os.path.exists(filename):
|
||||
containerfile_path = filename
|
||||
break
|
||||
return containerfile_path
|
||||
|
||||
def _get_containerfile_contents(self):
|
||||
"""
|
||||
Get the path to the Containerfile for an invocation
|
||||
of the module, and return its contents.
|
||||
|
||||
See if either `file` or `container_file` in build args are populated,
|
||||
fetch their contents if so. If not, return the contents of the Containerfile
|
||||
or Dockerfile from inside the build context, if present.
|
||||
|
||||
If we don't find a Containerfile/Dockerfile in any of the above
|
||||
locations, return 'None'.
|
||||
"""
|
||||
|
||||
build_file_arg = self.build.get('file') if self.build else None
|
||||
containerfile_contents = self.build.get('container_file') if self.build else None
|
||||
|
||||
container_filename = None
|
||||
if build_file_arg:
|
||||
container_filename = build_file_arg
|
||||
elif self.path and not build_file_arg:
|
||||
container_filename = self._find_containerfile_from_context()
|
||||
|
||||
if not containerfile_contents:
|
||||
with open(container_filename) as f:
|
||||
containerfile_contents = f.read()
|
||||
|
||||
return containerfile_contents
|
||||
|
||||
def _hash_containerfile_contents(self, containerfile_contents):
|
||||
"""
|
||||
When given the contents of a Containerfile/Dockerfile,
|
||||
return a sha256 hash of these contents.
|
||||
"""
|
||||
return hashlib.sha256(
|
||||
containerfile_contents.encode(),
|
||||
usedforsecurity=False
|
||||
).hexdigest()
|
||||
|
||||
def _get_args_containerfile_hash(self):
|
||||
"""
|
||||
If we can find a Containerfile in any of the module args
|
||||
or inside the build context, hash its contents.
|
||||
|
||||
If we don't have this, return an empty string.
|
||||
"""
|
||||
|
||||
args_containerfile_hash = ""
|
||||
|
||||
context_has_containerfile = self.path and self._find_containerfile_from_context()
|
||||
|
||||
should_hash_args_containerfile = (
|
||||
context_has_containerfile or
|
||||
self.build.get('file') is not None or
|
||||
self.build.get('container_file') is not None
|
||||
)
|
||||
|
||||
if should_hash_args_containerfile:
|
||||
args_containerfile_hash = self._hash_containerfile_contents(
|
||||
self._get_containerfile_contents()
|
||||
)
|
||||
return args_containerfile_hash
|
||||
|
||||
def present(self):
|
||||
image = self.find_image()
|
||||
|
||||
existing_image_containerfile_hash = ""
|
||||
args_containerfile_hash = self._get_args_containerfile_hash()
|
||||
|
||||
if image:
|
||||
digest_before = image[0].get('Digest', image[0].get('digest'))
|
||||
labels = image[0].get('Labels') or {}
|
||||
if "containerfile.hash" in labels:
|
||||
existing_image_containerfile_hash = labels["containerfile.hash"]
|
||||
else:
|
||||
digest_before = None
|
||||
|
||||
if not image or self.force:
|
||||
both_hashes_exist_and_differ = (
|
||||
args_containerfile_hash != "" and
|
||||
existing_image_containerfile_hash != "" and
|
||||
args_containerfile_hash != existing_image_containerfile_hash
|
||||
)
|
||||
|
||||
if not image or self.force or both_hashes_exist_and_differ:
|
||||
if self.state == 'build' or self.path:
|
||||
# Build the image
|
||||
build_file = self.build.get('file') if self.build else None
|
||||
|
|
@ -513,7 +601,7 @@ class PodmanImageManager(object):
|
|||
self.results['actions'].append('Built image {image_name} from {path}'.format(
|
||||
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()
|
||||
self.results['image'], self.results['stdout'] = self.build_image(args_containerfile_hash)
|
||||
image = self.results['image']
|
||||
else:
|
||||
# Pull the image
|
||||
|
|
@ -649,7 +737,7 @@ class PodmanImageManager(object):
|
|||
msg='Failed to pull image {image_name}'.format(image_name=image_name))
|
||||
return self.inspect_image(out.strip())
|
||||
|
||||
def build_image(self):
|
||||
def build_image(self, containerfile_hash):
|
||||
args = ['build']
|
||||
args.extend(['-t', self.image_name])
|
||||
|
||||
|
|
@ -698,6 +786,9 @@ class PodmanImageManager(object):
|
|||
f.write(container_file_txt)
|
||||
args.extend(['--file', container_file_path])
|
||||
|
||||
if containerfile_hash:
|
||||
args.extend(['--label', f"containerfile.hash={containerfile_hash}"])
|
||||
|
||||
volume = self.build.get('volume')
|
||||
if volume:
|
||||
for v in volume:
|
||||
|
|
|
|||
|
|
@ -237,6 +237,29 @@
|
|||
register: oci_build6
|
||||
ignore_errors: true
|
||||
|
||||
- name: Build OCI image testimage6 twice with the same Containerfile
|
||||
containers.podman.podman_image:
|
||||
executable: "{{ test_executable | default('podman') }}"
|
||||
name: testimage6
|
||||
state: build
|
||||
build:
|
||||
format: oci
|
||||
container_file: |-
|
||||
FROM quay.io/coreos/alpine-sh
|
||||
register: oci_build7
|
||||
loop: [0, 1]
|
||||
|
||||
- name: Build OCI image testimage6 with a different Containerfile
|
||||
containers.podman.podman_image:
|
||||
executable: "{{ test_executable | default('podman') }}"
|
||||
name: testimage6
|
||||
state: build
|
||||
build:
|
||||
format: oci
|
||||
container_file: |-
|
||||
FROM docker.io/alpine
|
||||
register: oci_build8
|
||||
|
||||
- name: Inspect first image
|
||||
containers.podman.podman_image_info:
|
||||
executable: "{{ test_executable | default('podman') }}"
|
||||
|
|
@ -259,6 +282,13 @@
|
|||
- oci_build4 is success
|
||||
- oci_build5 is success
|
||||
- oci_build6 is failed
|
||||
# The following line tests that building an image twice with
|
||||
# the same Containerfile doesn't rebuild the image.
|
||||
- oci_build7.results[1] is not changed
|
||||
# oci_build8 tests that building an image with the same name
|
||||
# but a different Containerfile results in a new image being
|
||||
# built.
|
||||
- oci_build8 is changed
|
||||
- "'localhost/testimage:latest' in testimage_info.images[0]['RepoTags'][0]"
|
||||
- "'localhost/testimage2:latest' in testimage2_info.images[0]['RepoTags'][0]"
|
||||
- "'no such file or directory' in oci_build3.msg"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue