Add option to control time that chache lives
This commit is contained in:
parent
616f4077f5
commit
630b7dd086
@ -1,8 +1,8 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/python3
|
||||||
|
|
||||||
from typing import Dict, List, Union
|
from typing import Dict, List, Union
|
||||||
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
import contextlib
|
import contextlib
|
||||||
import datetime
|
import datetime
|
||||||
import fcntl
|
import fcntl
|
||||||
@ -10,6 +10,7 @@ import logging
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import subprocess
|
import subprocess
|
||||||
|
import time
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
@ -75,7 +76,6 @@ class CB:
|
|||||||
self.work_dir = data_dir / 'work'
|
self.work_dir = data_dir / 'work'
|
||||||
self.out_dir = data_dir / 'out'
|
self.out_dir = data_dir / 'out'
|
||||||
|
|
||||||
self.date = datetime.date.today().strftime('%Y%m%d')
|
|
||||||
self.service_default_state = 'enabled'
|
self.service_default_state = 'enabled'
|
||||||
self.created_scripts: List[Path] = []
|
self.created_scripts: List[Path] = []
|
||||||
self._build_errors: List[BuildError] = []
|
self._build_errors: List[BuildError] = []
|
||||||
@ -165,6 +165,17 @@ class CB:
|
|||||||
if self.external_files:
|
if self.external_files:
|
||||||
self.external_files = self.expand_path(Path(self.external_files))
|
self.external_files = self.expand_path(Path(self.external_files))
|
||||||
|
|
||||||
|
rebuild_after = cfg.get('rebuild_after', {'days': 1})
|
||||||
|
try:
|
||||||
|
self.rebuild_after = datetime.timedelta(**rebuild_after)
|
||||||
|
except TypeError as e:
|
||||||
|
m = re.match(r"'([^']+)'", str(e))
|
||||||
|
if m:
|
||||||
|
arg = m.groups()[0]
|
||||||
|
raise Error(f'Invalid key `{arg}` passed to rebuild_after')
|
||||||
|
else:
|
||||||
|
raise
|
||||||
|
|
||||||
self._packages = cfg.get('packages', {})
|
self._packages = cfg.get('packages', {})
|
||||||
self._services = cfg.get('services', {})
|
self._services = cfg.get('services', {})
|
||||||
self._scripts = cfg.get('scripts', {})
|
self._scripts = cfg.get('scripts', {})
|
||||||
@ -464,6 +475,17 @@ Dir::Etc::preferencesparts "/var/empty";
|
|||||||
else:
|
else:
|
||||||
self.error(BuildError(target, arch))
|
self.error(BuildError(target, arch))
|
||||||
|
|
||||||
|
def should_rebuild(self, tarball):
|
||||||
|
if not os.path.exists(tarball):
|
||||||
|
rebuild = True
|
||||||
|
else:
|
||||||
|
lived = time.time() - os.path.getmtime(tarball)
|
||||||
|
delta = datetime.timedelta(seconds=lived)
|
||||||
|
rebuild = delta > self.rebuild_after
|
||||||
|
if rebuild:
|
||||||
|
os.unlink(tarball)
|
||||||
|
return rebuild
|
||||||
|
|
||||||
def build_tarball(
|
def build_tarball(
|
||||||
self,
|
self,
|
||||||
target: str,
|
target: str,
|
||||||
@ -476,10 +498,11 @@ Dir::Etc::preferencesparts "/var/empty";
|
|||||||
target = f'{target}_{self.escape_branch(branch)}'
|
target = f'{target}_{self.escape_branch(branch)}'
|
||||||
image = re.sub(r'.*/', '', target)
|
image = re.sub(r'.*/', '', target)
|
||||||
full_target = f'{target}.{kind}'
|
full_target = f'{target}.{kind}'
|
||||||
tarball = self.out_dir / f'{image}-{self.date}-{arch}.{kind}'
|
tarball_name = f'{image}-{arch}.{kind}'
|
||||||
|
tarball_path = self.out_dir / tarball_name
|
||||||
apt_dir = self.work_dir / 'apt'
|
apt_dir = self.work_dir / 'apt'
|
||||||
with self.pushd(self.work_dir / 'mkimage-profiles'):
|
with self.pushd(self.work_dir / 'mkimage-profiles'):
|
||||||
if tarball.exists():
|
if not self.should_rebuild(tarball_path):
|
||||||
self.info(f'Skip building of {full_target} {arch}')
|
self.info(f'Skip building of {full_target} {arch}')
|
||||||
else:
|
else:
|
||||||
cmd = [
|
cmd = [
|
||||||
@ -487,17 +510,19 @@ Dir::Etc::preferencesparts "/var/empty";
|
|||||||
f'APTCONF={apt_dir}/apt.conf.{branch}.{arch}',
|
f'APTCONF={apt_dir}/apt.conf.{branch}.{arch}',
|
||||||
f'ARCH={arch}',
|
f'ARCH={arch}',
|
||||||
f'IMAGE_OUTDIR={self.out_dir}',
|
f'IMAGE_OUTDIR={self.out_dir}',
|
||||||
|
f'IMAGE_OUTFILE={tarball_name}',
|
||||||
full_target,
|
full_target,
|
||||||
]
|
]
|
||||||
self.info(f'Begin building of {full_target} {arch}')
|
self.info(f'Begin building of {full_target} {arch}')
|
||||||
self.call(cmd)
|
self.call(cmd)
|
||||||
if os.path.exists(tarball):
|
|
||||||
|
if os.path.exists(tarball_path):
|
||||||
self.info(f'End building of {full_target} {arch}')
|
self.info(f'End building of {full_target} {arch}')
|
||||||
else:
|
else:
|
||||||
self.build_failed(full_target, arch)
|
self.build_failed(full_target, arch)
|
||||||
tarball = None
|
tarball_path = None
|
||||||
|
|
||||||
return tarball
|
return tarball_path
|
||||||
|
|
||||||
def image_path(
|
def image_path(
|
||||||
self,
|
self,
|
||||||
@ -525,7 +550,9 @@ Dir::Etc::preferencesparts "/var/empty";
|
|||||||
def remove_old_tarballs(self):
|
def remove_old_tarballs(self):
|
||||||
with self.pushd(self.out_dir):
|
with self.pushd(self.out_dir):
|
||||||
for tb in os.listdir():
|
for tb in os.listdir():
|
||||||
if not re.search(f'-{self.date}-', tb):
|
lived = time.time() - os.path.getmtime(tb)
|
||||||
|
delta = datetime.timedelta(seconds=lived)
|
||||||
|
if delta > self.rebuild_after:
|
||||||
os.unlink(tb)
|
os.unlink(tb)
|
||||||
|
|
||||||
def ensure_scripts(self, image):
|
def ensure_scripts(self, image):
|
||||||
|
@ -7,6 +7,12 @@ log_level: info
|
|||||||
bad_arches:
|
bad_arches:
|
||||||
- armh
|
- armh
|
||||||
|
|
||||||
|
rebuild_after:
|
||||||
|
weeks: 0
|
||||||
|
days: 0
|
||||||
|
hours: 24
|
||||||
|
minutes: 10
|
||||||
|
|
||||||
external_files: ~/external_files
|
external_files: ~/external_files
|
||||||
|
|
||||||
images:
|
images:
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from collections.abc import Iterable, Callable
|
from collections.abc import Iterable, Callable
|
||||||
|
|
||||||
import datetime
|
|
||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
import re
|
import re
|
||||||
@ -157,8 +156,7 @@ def make(args):
|
|||||||
|
|
||||||
match = re.match(r'.*/([-\w]*)\.(.*)', args[-1])
|
match = re.match(r'.*/([-\w]*)\.(.*)', args[-1])
|
||||||
target, kind = match.groups()
|
target, kind = match.groups()
|
||||||
date = datetime.date.today().strftime('%Y%m%d')
|
image = out_dir / f'{target}-{arch}.{kind}'
|
||||||
image = out_dir / f'{target}-{date}-{arch}.{kind}'
|
|
||||||
image.write_bytes(
|
image.write_bytes(
|
||||||
bytes(random.randint(0, 255) for x in range(random.randint(32, 128)))
|
bytes(random.randint(0, 255) for x in range(random.randint(32, 128)))
|
||||||
)
|
)
|
||||||
|
@ -96,3 +96,8 @@ class TestErrors(TestCase):
|
|||||||
regex,
|
regex,
|
||||||
cloud_build.create_images
|
cloud_build.create_images
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_rebuild_after_format(self):
|
||||||
|
regex = 'years.*rebuild_after'
|
||||||
|
self.kwargs.update(config='tests/test_rebuild_after_format.yaml')
|
||||||
|
self.assertRaisesRegex(Error, regex, CB, **self.kwargs)
|
||||||
|
50
tests/test_rebuild.py
Normal file
50
tests/test_rebuild.py
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
from pathlib import Path
|
||||||
|
from unittest import TestCase
|
||||||
|
from unittest import mock
|
||||||
|
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import tempfile
|
||||||
|
import time
|
||||||
|
|
||||||
|
from cloud_build import CB
|
||||||
|
from cloud_build import BuildError
|
||||||
|
|
||||||
|
import tests.call as call
|
||||||
|
|
||||||
|
|
||||||
|
DS = {'make': [call.return_d(0), call.nop_d]}
|
||||||
|
|
||||||
|
|
||||||
|
class TestErrors(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.data_dir = Path(tempfile.mkdtemp(prefix='cloud_build'))
|
||||||
|
self.cb = CB(
|
||||||
|
config='tests/test_rebuild.yaml',
|
||||||
|
data_dir=self.data_dir,
|
||||||
|
no_tests=True,
|
||||||
|
create_remote_dirs=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
shutil.rmtree(self.data_dir)
|
||||||
|
|
||||||
|
@mock.patch('subprocess.call', call.Call(decorators=DS))
|
||||||
|
def test_do_rebuild(self):
|
||||||
|
tarball = self.data_dir / 'out/docker_Sisyphus-x86_64.tar.xz'
|
||||||
|
tarball.touch()
|
||||||
|
two_hours_ago = time.time() - 2*60*60
|
||||||
|
os.utime(tarball, times=(two_hours_ago, two_hours_ago))
|
||||||
|
msg = 'Do not try to rebuild with outdated cache'
|
||||||
|
with self.assertRaises(BuildError, msg=msg):
|
||||||
|
self.cb.create_images()
|
||||||
|
|
||||||
|
@mock.patch('subprocess.call', call.Call(decorators=DS))
|
||||||
|
def test_dont_rebuild(self):
|
||||||
|
tarball = self.data_dir / 'out/docker_Sisyphus-x86_64.tar.xz'
|
||||||
|
tarball.touch()
|
||||||
|
msg = 'Try to rebuild with valid cache'
|
||||||
|
try:
|
||||||
|
self.cb.create_images()
|
||||||
|
except BuildError:
|
||||||
|
self.fail(msg)
|
18
tests/test_rebuild.yaml
Normal file
18
tests/test_rebuild.yaml
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
---
|
||||||
|
remote: '/var/empty'
|
||||||
|
key: 0x00000000
|
||||||
|
|
||||||
|
rebuild_after:
|
||||||
|
hours: 1
|
||||||
|
|
||||||
|
images:
|
||||||
|
rootfs-minimal:
|
||||||
|
target: ve/docker
|
||||||
|
kinds:
|
||||||
|
- tar.xz
|
||||||
|
|
||||||
|
branches:
|
||||||
|
Sisyphus:
|
||||||
|
arches:
|
||||||
|
x86_64:
|
||||||
|
...
|
19
tests/test_rebuild_after_format.yaml
Normal file
19
tests/test_rebuild_after_format.yaml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
---
|
||||||
|
remote: '/var/empty'
|
||||||
|
key: 0x00000000
|
||||||
|
|
||||||
|
rebuild_after:
|
||||||
|
days: 3
|
||||||
|
years: 10
|
||||||
|
|
||||||
|
images:
|
||||||
|
rootfs-minimal:
|
||||||
|
target: ve/docker
|
||||||
|
kinds:
|
||||||
|
- tar.xz
|
||||||
|
|
||||||
|
branches:
|
||||||
|
Sisyphus:
|
||||||
|
arches:
|
||||||
|
x86_64:
|
||||||
|
...
|
Loading…
x
Reference in New Issue
Block a user