diff --git a/shlax/actions/packages.py b/shlax/actions/packages.py index 523bd1a..cbdb385 100644 --- a/shlax/actions/packages.py +++ b/shlax/actions/packages.py @@ -163,9 +163,3 @@ class Packages: def __repr__(self): return f'Packages({self.packages}, upgrade={self.upgrade})' - - def layerhasher(self, parent=None): - import hashlib - blurb = (parent or '') + repr(self) - sha1 = hashlib.sha1(blurb.encode('ascii')) - return sha1.hexdigest() diff --git a/shlax/image.py b/shlax/image.py index 1a01869..be4460a 100644 --- a/shlax/image.py +++ b/shlax/image.py @@ -1,3 +1,4 @@ +import copy import os import re @@ -74,3 +75,8 @@ class Image: for tag in self.tags: await action.exec('buildah', 'push', f'{self.repository}:{tag}') + + def layer(self, key): + layer = copy.deepcopy(self) + layer.tags = [key] + return layer diff --git a/shlax/output.py b/shlax/output.py index 04ef6f2..2dced2f 100644 --- a/shlax/output.py +++ b/shlax/output.py @@ -141,6 +141,16 @@ class Output: '\n', ])) + def skip(self, action): + if self.debug is True or 'visit' in str(self.debug): + self(''.join([ + self.colors['yellowbold'], + '↪️ SKIP ', + self.colors['reset'], + self.colorized(action), + '\n', + ])) + def success(self, action): if self.debug is True or 'visit' in str(self.debug): self(''.join([ diff --git a/shlax/targets/buildah.py b/shlax/targets/buildah.py index cfd9a57..fa78596 100644 --- a/shlax/targets/buildah.py +++ b/shlax/targets/buildah.py @@ -1,4 +1,5 @@ import copy +import hashlib import json import os import sys @@ -11,6 +12,8 @@ from ..proc import Proc class Buildah(Target): + """Build container image with buildah""" + def __init__(self, *actions, base=None, commit=None, @@ -51,50 +54,64 @@ class Buildah(Target): if actions: actions = actions[len(actions_done):] + if not actions: + self.clean = None + self.output.success('Image up to date') + return else: self.actions = self.actions[len(actions_done):] + if not self.actions: + self.clean = None + self.output.success('Image up to date') + return self.ctr = (await self.parent.exec('buildah', 'from', self.base)).out self.mnt = Path((await self.parent.exec('buildah', 'mount', self.ctr)).out) await super().__call__(*actions) - async def cache_load(self, *actions): - actions_done = [] + async def images(self): result = await self.parent.exec( 'podman image list --format json', quiet=True, ) result = json.loads(result.out) - images = [item for sublist in result for item in sublist['History']] - self.hash_previous = None - for action in actions or self.actions: - hasher = getattr(action, 'layerhasher', None) - if not hasher: - break - layerhash = hasher(self.hash_previous) - layerimage = copy.deepcopy(self.image) - layerimage.tags = [layerhash] - if 'localhost/' + str(layerimage) in images: - self.base = str(layerimage) - self.hash_previous = layerhash - actions_done.append(action) - self.output.success(f'Found cached layer for {action}') + return [item for sublist in result for item in sublist['History']] + async def cache_load(self, *actions): + actions_done = [] + self.image_previous = Image(self.base) + images = await self.images() + for action in actions or self.actions: + action_image = self.action_image(action) + if 'localhost/' + str(action_image) in images: + self.base = self.image_previous = action_image + actions_done.append(action) + self.output.skip(f'Found valid cached layer for {action}') + else: + break return actions_done + def action_image(self, action): + if self.image_previous: + prefix = self.image_previous.tags[0] + else: + prefix = self.base + key = prefix + repr(action) + sha1 = hashlib.sha1(key.encode('ascii')) + return self.image.layer(sha1.hexdigest()) + async def action(self, action, reraise=False): result = await super().action(action, reraise) - hasher = getattr(action, 'layerhasher', None) - if hasher: - layerhash = hasher(self.hash_previous if self.hash_previous else None) - layerimage = copy.deepcopy(self.image) - layerimage.tags = [layerhash] - await self.commit(layerimage) + action_image = self.action_image(action) + await self.commit(action_image) + self.image_previous = action_image return result async def clean(self, target): for src, dst in self.mounts.items(): await self.parent.exec('umount', self.mnt / str(dst)[1:]) + else: + return if self.result.status == 'success': await self.commit()