# -*- coding: utf-8 -*- import hashlib import json import os import subprocess import sys from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.primitives.serialization import load_pem_private_key PKG_ROOT = '/srv/build/lxc' PRIVATE_KEY = '/srv/build/packages.key' LXC_STORAGE = '/var/lib/lxc/storage' class LXCPacker: def __init__(self, image): self.image = image self.tar_path = None self.xz_path = None def pack(self): # Prepare package file names self.tar_path = os.path.join(PKG_ROOT, '{}.tar'.format(self.image.upper_layer)) self.xz_path = '{}.xz'.format(self.tar_path) if os.path.exists(self.xz_path): print('Package {} already exists, skipping packaging tasks'.format(self.xz_path)) return os.makedirs(PKG_ROOT, 0o755, True) self.create_archive() self.register_package() self.sign_packages() def create_archive(self): # Create archive print('Archiving', self.image.upper_layer) subprocess.run(['tar', '--xattrs', '-cpf', self.tar_path, os.path.join(LXC_STORAGE, self.image.upper_layer)], cwd='/') # Add install/upgrade/uninstall scripts scripts = ('install', 'install.sh', 'upgrade', 'upgrade.sh', 'uninstall', 'uninstall.sh') scripts = [s for s in scripts if os.path.exists(os.path.join(self.image.build_dir, s))] subprocess.run(['tar', '--transform', 's|^|srv/{}/|'.format(self.image.upper_layer), '-rpf', self.tar_path] + scripts, cwd=self.image.build_dir) # 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_package(self): # Prepare metadata meta = self.image.meta.copy() meta['lxc'] = {} for key in ('layers', 'mounts', 'env', 'cmd', 'cwd', 'uid', 'gid', 'halt'): value = getattr(self.image, key) if value: meta['lxc'][key] = value # Register package print('Registering package') packages = {} packages_file = os.path.join(PKG_ROOT, 'packages') if os.path.exists(packages_file): with open(packages_file, 'r') as f: packages = json.load(f) packages[self.image.name] = meta packages[self.image.name]['size'] = os.path.getsize(self.xz_path) packages[self.image.name]['sha512'] = hash_file(self.xz_path) with open(packages_file, 'w') as f: json.dump(packages, f, sort_keys=True, indent=4) def sign_packages(self): # Sign packages file print('Signing packages') with open(PRIVATE_KEY, 'rb') as f: priv_key = load_pem_private_key(f.read(), None, default_backend()) with open(os.path.join(PKG_ROOT, 'packages'), 'rb') as f: data = f.read() with open(os.path.join(PKG_ROOT, 'packages.sig'), 'wb') as f: f.write(priv_key.sign(data, ec.ECDSA(hashes.SHA512()))) def hash_file(file_path): sha512 = hashlib.sha512() with open(file_path, 'rb') as f: while True: data = f.read(65536) if not data: break sha512.update(data) return sha512.hexdigest()