# -*- coding: utf-8 -*- import json import os import subprocess import sys from lxcmgr.paths import LXC_STORAGE_DIR from lxcmgr.pkgmgr import PkgMgr from . import crypto from .builder import ImageNotFoundError from .paths import PRIVATE_KEY, REPO_APPS_DIR, REPO_IMAGES_DIR, REPO_META_FILE, REPO_SIG_FILE class PackageExistsError(Exception): pass class Packer: def __init__(self): self.app = None self.image = None self.tar_path = None self.xz_path = None if os.path.exists(REPO_META_FILE): with open(REPO_META_FILE, 'r') as f: self.packages = json.load(f) else: self.packages = {'apps': {}, 'images': {}} def save_repo_meta(self): with open(REPO_META_FILE, 'w') as f: json.dump(self.packages, f, sort_keys=True, indent=4) def pack_image(self, image, force): self.image = image # Prepare package file names self.tar_path = os.path.join(REPO_IMAGES_DIR, '{}.tar'.format(self.image.name)) self.xz_path = '{}.xz'.format(self.tar_path) if force: self.unregister_image() try: os.unlink(self.xz_path) except FileNotFoundError: pass elif os.path.exists(self.xz_path): raise PackageExistsError(self.xz_path) self.create_image_archive() self.register_image() self.sign_packages() def create_image_archive(self): # Create archive print('Archiving', self.image.path) subprocess.run(['tar', '--xattrs', '-cpf', self.tar_path, self.image.name], cwd=LXC_STORAGE_DIR) self.compress_archive() def compress_archive(self): # Compress the tarball with xz (LZMA2) print('Compressing', self.tar_path, '({:.2f} MB)'.format(os.path.getsize(self.tar_path)/1048576)) subprocess.run(['xz', '-9', self.tar_path]) print('Compressed ', self.xz_path, '({:.2f} MB)'.format(os.path.getsize(self.xz_path)/1048576)) def register_image(self): # Register image in global repository metadata file print('Registering package {}'.format(self.image.name)) self.packages['images'][self.image.name] = self.image.conf.copy() self.packages['images'][self.image.name]['size'] = os.path.getsize(self.xz_path) self.packages['images'][self.image.name]['sha512'] = crypto.hash_file(self.xz_path) self.save_repo_meta() # Register the image also to locally installed images for package manager pm = PkgMgr() pm.register_image(self.image.name, self.packages['images'][self.image.name]) def sign_packages(self): signature = crypto.sign_file(PRIVATE_KEY, REPO_META_FILE) with open(REPO_SIG_FILE, 'wb') as f: f.write(signature) def unregister_image(self): # Removes package from global repository metadata file if self.image.name in self.packages['images']: del self.packages['images'][self.image.name] self.save_repo_meta() def pack_app(self, app): self.app = app # Check if all images exist for container in app.conf['containers']: image = app.conf['containers'][container]['image'] if image not in self.packages['images']: raise ImageNotFoundError(image) # Prepare package file names self.tar_path = os.path.join(REPO_APPS_DIR, '{}.tar'.format(self.app.name)) self.xz_path = '{}.xz'.format(self.tar_path) try: os.unlink(self.xz_path) except FileNotFoundError: pass self.create_app_archive() self.register_app() self.sign_packages() def create_app_archive(self): # Create archive with application setup scripts print('Archiving setup scripts for', self.app.name) scripts = ('install', 'install.sh', 'upgrade', 'upgrade.sh', 'uninstall', 'uninstall.sh') scripts = [s for s in scripts if os.path.exists(os.path.join(self.app.build_dir, s))] subprocess.run(['tar', '--xattrs', '-cpf', self.tar_path, '--transform', 's,^,{}/,'.format(self.app.name)] + scripts, cwd=self.app.build_dir) self.compress_archive() def register_app(self): # Register package in global repository metadata file print('Registering package {}'.format(self.app.name)) self.packages['apps'][self.app.name] = self.app.conf.copy() self.packages['apps'][self.app.name]['size'] = os.path.getsize(self.xz_path) self.packages['apps'][self.app.name]['sha512'] = crypto.hash_file(self.xz_path) self.save_repo_meta()