add build timestamp to avoid useless image builds

* skip build if image timestamp is newer than config file
This commit is contained in:
Gilles Grandou 2023-12-04 23:32:05 +01:00
parent 8ccd734c02
commit 3462964cb0
2 changed files with 31 additions and 1 deletions

View File

@ -10,6 +10,8 @@ import os
import re import re
import xdg.BaseDirectory import xdg.BaseDirectory
import yaml import yaml
import datetime
import pytz
import subprocess import subprocess
from pprint import pprint from pprint import pprint
@ -33,6 +35,7 @@ def read_yaml(conf_file):
try: try:
with open(conf_file, 'r') as file: with open(conf_file, 'r') as file:
conf = yaml.safe_load(file) conf = yaml.safe_load(file)
conf['stamp'] = datetime.datetime.fromtimestamp(os.path.getmtime(conf_file), tz=pytz.UTC)
return conf return conf
except yaml.YAMLError as e: except yaml.YAMLError as e:
print(f'ERROR: bad configuration file:') print(f'ERROR: bad configuration file:')
@ -62,6 +65,7 @@ def load_config(conf_file, osname):
print(f"ERROR: cannot find configuration for distribution {osname}") print(f"ERROR: cannot find configuration for distribution {osname}")
sys.exit(1) sys.exit(1)
osconf['stamp'] = conf.get('stamp')
osconf['osname'] = osname osconf['osname'] = osname
for k in [ 'dockerfile', 'packages', 'environment', 'binds' ]: for k in [ 'dockerfile', 'packages', 'environment', 'binds' ]:
if osconf.get(k): if osconf.get(k):
@ -83,6 +87,24 @@ def make_image_name(osname):
def build_image(client, conf, update, verbose): def build_image(client, conf, update, verbose):
osname = conf.get('osname')
image_name = 'runon-{}'.format(osname)
tag = make_image_name(conf['osname'])
cache_dir = os.path.join(xdg.BaseDirectory.xdg_cache_home, 'runon')
cache_file = os.path.join(cache_dir, image_name)
if not update and os.path.exists(cache_file):
ts_image = datetime.datetime.fromtimestamp(os.path.getmtime(cache_file), tz=pytz.UTC)
ts_conf = conf.get('stamp')
if verbose:
print('config: {}'.format(ts_conf))
print('image: {}'.format(ts_image))
if ts_image and ts_image > ts_conf:
if verbose:
print('image: {} up-to-date'.format(image_name))
image = client.images.get(tag)
return image
image = conf.get('image') image = conf.get('image')
packages = conf['packages'] packages = conf['packages']
dockerfile = conf['dockerfile'] dockerfile = conf['dockerfile']
@ -91,7 +113,6 @@ def build_image(client, conf, update, verbose):
dockerfile.insert(0, 'FROM {}'.format(image)) dockerfile.insert(0, 'FROM {}'.format(image))
for p in packages: for p in packages:
dockerfile.append(pkginstall.format(p)) dockerfile.append(pkginstall.format(p))
tag = make_image_name(conf['osname'])
try: try:
if verbose: if verbose:
# fallback to external command 'docker build' as there is # fallback to external command 'docker build' as there is
@ -112,6 +133,14 @@ def build_image(client, conf, update, verbose):
print('Built image {} / {}'.format(image.tags[0], image.short_id)) print('Built image {} / {}'.format(image.tags[0], image.short_id))
for l in logs: for l in logs:
print(l.get('stream', '').strip('\n')) print(l.get('stream', '').strip('\n'))
if not os.path.exists(cache_dir):
os.mkdir(cache_dir)
with open(cache_file, 'w') as file:
if verbose:
print('cache: {}'.format(cache_file))
file.write('')
except (docker.errors.BuildError, KeyboardInterrupt, subprocess.CalledProcessError, docker.errors.ImageNotFound) as e: except (docker.errors.BuildError, KeyboardInterrupt, subprocess.CalledProcessError, docker.errors.ImageNotFound) as e:
print('Build Error: {}'.format(e)) print('Build Error: {}'.format(e))
print() print()

View File

@ -14,6 +14,7 @@ setup(
"pathlib", "pathlib",
"pyxdg", "pyxdg",
"pyyaml", "pyyaml",
"pytz",
], ],
entry_points={ entry_points={
"console_scripts": [ "console_scripts": [