Introduce BuildType for normal, force, scratch and metadata builds

This commit is contained in:
Disassembler 2019-11-30 15:56:29 +01:00
parent e1b7ba1204
commit e794ced82a
Signed by: Disassembler
GPG Key ID: 524BD33A0EE29499
4 changed files with 36 additions and 13 deletions

View File

@ -5,7 +5,7 @@ import argparse
import os import os
import sys import sys
from lxcbuild.app import App from lxcbuild.app import App
from lxcbuild.image import Image from lxcbuild.image import BuildType, Image
parser = argparse.ArgumentParser(description='VM application builder and packager') parser = argparse.ArgumentParser(description='VM application builder and packager')
group = parser.add_mutually_exclusive_group() group = parser.add_mutually_exclusive_group()
@ -22,8 +22,10 @@ args = parser.parse_args()
def build_and_pack_image(path, args): def build_and_pack_image(path, args):
image = Image() image = Image()
image.force_build = args.force or args.scratch if args.scratch:
image.scratch_build = args.scratch image.build_type = BuildType.SCRATCH
elif args.force:
image.build_type = BuildType.FORCE
image.build_and_pack(path) image.build_and_pack(path)
def pack_app(path): def pack_app(path):

View File

@ -3,43 +3,55 @@
import os import os
import sys import sys
from enum import Enum
from lxcmgr import lxcmgr from lxcmgr import lxcmgr
from .imagebuilder import ImageBuilder, ImageExistsError, ImageNotFoundError from .imagebuilder import ImageBuilder, ImageExistsError, ImageNotFoundError
from .imagepacker import ImagePacker from .imagepacker import ImagePacker
from .packer import PackageExistsError from .packer import PackageExistsError
class BuildType(Enum):
NORMAL = 1
FORCE = 2
SCRATCH = 3
METADATA = 4
class Image: class Image:
def __init__(self): def __init__(self):
self.name = None self.name = None
self.conf = {} self.conf = {}
self.lxcfile = None self.lxcfile = None
self.build_dir = None self.build_dir = None
self.force_build = False self.build_type = BuildType.NORMAL
self.scratch_build = False self.pack = False
def build_and_pack(self, lxcfile): def build_and_pack(self, lxcfile):
self.lxcfile = lxcfile self.lxcfile = lxcfile
self.build_dir = os.path.dirname(lxcfile) self.build_dir = os.path.dirname(lxcfile)
self.conf['build'] = True self.conf['build'] = True
try:
builder = ImageBuilder(self) builder = ImageBuilder(self)
try:
builder.build() builder.build()
# Packaging needs to happen in any case after a successful build in order to prevent outdated packages # Packaging needs to happen in any case after a successful build in order to prevent outdated packages
self.force_build = True self.pack = True
except ImageExistsError as e: except ImageExistsError as e:
# If container already exists and build hasn't been forced, rerun the build just for metadata which are still needed for packaging
print('Image {} already exists, skipping build tasks'.format(e)) print('Image {} already exists, skipping build tasks'.format(e))
self.build_type = BuildType.METADATA
builder.build()
except ImageNotFoundError as e: except ImageNotFoundError as e:
# If one of the layers is missing, cleanup and die
print('Image {} not found, can\'t build {}'.format(e, self.name)) print('Image {} not found, can\'t build {}'.format(e, self.name))
builder.clean() builder.clean()
sys.exit(1) sys.exit(1)
except: except:
if not self.scratch_build: # If build fails with another exception, cleanup (unless we were doing scratch build) and re-raise
if not self.build_type == BuildType.SCRATCH:
builder.clean() builder.clean()
raise raise
del self.conf['build'] del self.conf['build']
# If we're doing a scratch build, regenerate the final LXC container configuration including ephemeral layer # If we're doing a scratch build, regenerate the final LXC container configuration including ephemeral layer
if self.scratch_build: if self.build_type == BuildType.SCRATCH:
lxcmgr.create_container(self.name, self.conf) lxcmgr.create_container(self.name, self.conf)
else: else:
try: try:

View File

@ -9,6 +9,8 @@ from lxcmgr import lxcmgr
from lxcmgr.paths import LXC_STORAGE_DIR from lxcmgr.paths import LXC_STORAGE_DIR
from lxcmgr.pkgmgr import PkgMgr from lxcmgr.pkgmgr import PkgMgr
from .image import BuildType
class ImageExistsError(Exception): class ImageExistsError(Exception):
pass pass
@ -65,6 +67,9 @@ class ImageBuilder:
def run_script(self, script): def run_script(self, script):
# Creates a temporary container, runs a script in its namespace, and stores the modifications as part of the image # Creates a temporary container, runs a script in its namespace, and stores the modifications as part of the image
if self.image.build_type == BuildType.METADATA:
# Don't run anything if we're building just metadata
return
lxcmgr.create_container(self.image.name, self.image.conf) lxcmgr.create_container(self.image.name, self.image.conf)
sh = os.path.join(LXC_STORAGE_DIR, self.image.name, 'run.sh') sh = os.path.join(LXC_STORAGE_DIR, self.image.name, 'run.sh')
with open(sh, 'w') as f: with open(sh, 'w') as f:
@ -73,7 +78,8 @@ class ImageBuilder:
os.chown(sh, 100000, 100000) os.chown(sh, 100000, 100000)
subprocess.run(['lxc-execute', self.image.name, '--', '/bin/sh', '-lc', '/run.sh'], check=True) subprocess.run(['lxc-execute', self.image.name, '--', '/bin/sh', '-lc', '/run.sh'], check=True)
os.unlink(sh) os.unlink(sh)
if not self.image.scratch_build: if not self.image.build_type == BuildType.SCRATCH:
# Don't delete the temporary container if we're doing scratch build
lxcmgr.destroy_container(self.image.name) lxcmgr.destroy_container(self.image.name)
def set_name(self, name): def set_name(self, name):
@ -82,7 +88,7 @@ class ImageBuilder:
self.image.conf['layers'] = [name] self.image.conf['layers'] = [name]
image_path = self.get_layer_path(name) image_path = self.get_layer_path(name)
if os.path.exists(image_path): if os.path.exists(image_path):
if self.image.force_build: if self.image.build_type in (BuildType.FORCE, BuildType.SCRATCH):
self.clean() self.clean()
else: else:
raise ImageExistsError(image_path) raise ImageExistsError(image_path)
@ -93,10 +99,13 @@ class ImageBuilder:
# Extend list of layers with the list of layers from parent image # Extend list of layers with the list of layers from parent image
# Raies an exception when IMAGE has no name # Raies an exception when IMAGE has no name
pkgmgr = PkgMgr() pkgmgr = PkgMgr()
self.image.conf['layers'].extend(pkgmgr.installed_packages[image]['layers']) self.image.conf['layers'].extend(pkgmgr.installed_packages['images'][image]['layers'])
def copy_files(self, src, dst): def copy_files(self, src, dst):
# Copy files from the host or download them from a http(s) URL # Copy files from the host or download them from a http(s) URL
if self.image.build_type == BuildType.METADATA:
# Don't copy anything if we're building just metadata
return
dst = os.path.join(LXC_STORAGE_DIR, self.image.name, dst) dst = os.path.join(LXC_STORAGE_DIR, self.image.name, dst)
if src.startswith('http://') or src.startswith('https://'): if src.startswith('http://') or src.startswith('https://'):
unpack_http_archive(src, dst) unpack_http_archive(src, dst)

View File

@ -19,7 +19,7 @@ class ImagePacker(Packer):
self.xz_path = '{}.xz'.format(self.tar_path) self.xz_path = '{}.xz'.format(self.tar_path)
def pack(self): def pack(self):
if self.image.force_build: if self.image.pack:
self.unregister() self.unregister()
try: try:
os.unlink(self.xz_path) os.unlink(self.xz_path)