Introduce BuildType for normal, force, scratch and metadata builds
This commit is contained in:
parent
e1b7ba1204
commit
e794ced82a
@ -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):
|
||||||
|
@ -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
|
||||||
|
builder = ImageBuilder(self)
|
||||||
try:
|
try:
|
||||||
builder = ImageBuilder(self)
|
|
||||||
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:
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user