Split client and server code.
This commit is contained in:
189
server/build_image.py
Executable file
189
server/build_image.py
Executable file
@@ -0,0 +1,189 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import argparse
|
||||
import os
|
||||
import shutil
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser(description='iconograph build_image')
|
||||
parser.add_argument(
|
||||
'--arch',
|
||||
dest='arch',
|
||||
action='store',
|
||||
default='amd64')
|
||||
parser.add_argument(
|
||||
'--archive',
|
||||
dest='archive',
|
||||
action='store',
|
||||
default='http://archive.ubuntu.com/ubuntu')
|
||||
parser.add_argument(
|
||||
'--dest-iso',
|
||||
dest='dest_iso',
|
||||
action='store',
|
||||
required=True)
|
||||
parser.add_argument(
|
||||
'--release',
|
||||
dest='release',
|
||||
action='store',
|
||||
required=True)
|
||||
parser.add_argument(
|
||||
'--shell',
|
||||
dest='shell',
|
||||
action='store',
|
||||
type=bool,
|
||||
default=False)
|
||||
parser.add_argument(
|
||||
'--source-iso',
|
||||
dest='source_iso',
|
||||
action='store',
|
||||
required=True)
|
||||
FLAGS = parser.parse_args()
|
||||
|
||||
|
||||
class ImageBuilder(object):
|
||||
|
||||
_BASE_PACKAGES = [
|
||||
'debconf',
|
||||
'devscripts',
|
||||
'dialog',
|
||||
'gnupg',
|
||||
'isc-dhcp-client',
|
||||
'locales',
|
||||
'nano',
|
||||
'net-tools',
|
||||
'iputils-ping',
|
||||
'sudo',
|
||||
'user-setup',
|
||||
'wget',
|
||||
]
|
||||
|
||||
def __init__(self, source_iso, dest_iso, archive, arch, release):
|
||||
self._source_iso = source_iso
|
||||
self._dest_iso = dest_iso
|
||||
self._archive = archive
|
||||
self._arch = arch
|
||||
self._release = release
|
||||
self._umount = []
|
||||
self._rmtree = []
|
||||
|
||||
def _Exec(self, *args):
|
||||
print('+', args)
|
||||
subprocess.check_call(args)
|
||||
|
||||
def _ExecChroot(self, chroot_path, *args):
|
||||
self._Exec('chroot', chroot_path, *args)
|
||||
|
||||
def _Debootstrap(self, root):
|
||||
path = os.path.join(root, 'chroot')
|
||||
os.mkdir(path)
|
||||
self._Exec(
|
||||
'debootstrap',
|
||||
'--variant=buildd',
|
||||
'--arch', self._arch,
|
||||
self._release,
|
||||
path,
|
||||
self._archive)
|
||||
return path
|
||||
|
||||
def _CreateUnion(self, root):
|
||||
iso_path = os.path.join(root, 'iso')
|
||||
os.mkdir(iso_path)
|
||||
self._Exec(
|
||||
'mount',
|
||||
'--options', 'loop,ro',
|
||||
self._source_iso,
|
||||
iso_path)
|
||||
self._umount.append(iso_path)
|
||||
|
||||
tmpfs_path = os.path.join(root, 'tmpfs')
|
||||
os.mkdir(tmpfs_path)
|
||||
self._Exec(
|
||||
'mount',
|
||||
'--types', 'tmpfs',
|
||||
'none',
|
||||
tmpfs_path)
|
||||
self._umount.append(tmpfs_path)
|
||||
|
||||
upper_path = os.path.join(tmpfs_path, 'upper')
|
||||
os.mkdir(upper_path)
|
||||
|
||||
work_path = os.path.join(tmpfs_path, 'work')
|
||||
os.mkdir(work_path)
|
||||
|
||||
union_path = os.path.join(root, 'union')
|
||||
os.mkdir(union_path)
|
||||
self._Exec(
|
||||
'mount',
|
||||
'--types', 'overlayfs',
|
||||
'--options', 'lowerdir=%s,upperdir=%s,workdir=%s' % (iso_path, upper_path, work_path),
|
||||
'none',
|
||||
union_path)
|
||||
self._umount.append(union_path)
|
||||
|
||||
return union_path
|
||||
|
||||
def _InstallPackages(self, chroot_path):
|
||||
self._ExecChroot(
|
||||
chroot_path,
|
||||
'apt-get',
|
||||
'install',
|
||||
'--assume-yes',
|
||||
*self._BASE_PACKAGES)
|
||||
self._ExecChroot(
|
||||
chroot_path,
|
||||
'apt-get',
|
||||
'clean')
|
||||
|
||||
def _Squash(self, chroot_path, union_path):
|
||||
self._Exec(
|
||||
'mksquashfs',
|
||||
chroot_path,
|
||||
os.path.join(union_path, 'casper', 'filesystem.squashfs'),
|
||||
'-noappend')
|
||||
|
||||
def _CreateISO(self, union_path):
|
||||
self._Exec(
|
||||
'grub-mkrescue',
|
||||
'--verbose',
|
||||
'--output=%s' % self._dest_iso,
|
||||
union_path)
|
||||
|
||||
def _BuildImage(self):
|
||||
root = tempfile.mkdtemp()
|
||||
self._rmtree.append(root)
|
||||
|
||||
print('Building image in:', root)
|
||||
|
||||
chroot_path = self._Debootstrap(root)
|
||||
union_path = self._CreateUnion(root)
|
||||
self._InstallPackages(chroot_path)
|
||||
if FLAGS.shell:
|
||||
self._Exec('bash')
|
||||
self._Squash(chroot_path, union_path)
|
||||
self._CreateISO(union_path)
|
||||
|
||||
def BuildImage(self):
|
||||
try:
|
||||
self._BuildImage()
|
||||
finally:
|
||||
pass
|
||||
for path in self._umount:
|
||||
self._Exec('umount', path)
|
||||
for path in self._rmtree:
|
||||
shutil.rmtree(path)
|
||||
|
||||
|
||||
def main():
|
||||
builder = ImageBuilder(
|
||||
FLAGS.source_iso,
|
||||
FLAGS.dest_iso,
|
||||
FLAGS.archive,
|
||||
FLAGS.arch,
|
||||
FLAGS.release)
|
||||
builder.BuildImage()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
90
server/build_manifest.py
Executable file
90
server/build_manifest.py
Executable file
@@ -0,0 +1,90 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser(description='iconograph fetcher')
|
||||
parser.add_argument(
|
||||
'--image-dir',
|
||||
dest='image_dir',
|
||||
action='store',
|
||||
required=True)
|
||||
parser.add_argument(
|
||||
'--image-type',
|
||||
dest='image_type',
|
||||
action='store',
|
||||
required=True)
|
||||
parser.add_argument(
|
||||
'--old-manifest',
|
||||
dest='old_manifest',
|
||||
action='store')
|
||||
FLAGS = parser.parse_args()
|
||||
|
||||
|
||||
class ManifestBuilder(object):
|
||||
|
||||
_FILE_REGEX = '^%(image_type)s\.(?P<timestamp>\d+)\.iso$'
|
||||
_BUF_SIZE = 2 ** 16
|
||||
|
||||
def __init__(self, image_dir, image_type, old_manifest):
|
||||
self._image_dir = image_dir
|
||||
self._file_regex = re.compile(self._FILE_REGEX % {
|
||||
'image_type': image_type,
|
||||
})
|
||||
self._old_manifest = old_manifest
|
||||
|
||||
def _Rollouts(self):
|
||||
if not self._old_manifest:
|
||||
return {}
|
||||
try:
|
||||
with open(self._old_manifest, 'r') as fh:
|
||||
parsed = json.load(fh)
|
||||
return dict(
|
||||
(image['timestamp'], image['rollout_‱'])
|
||||
for image in parsed['images'])
|
||||
except FileNotFoundError:
|
||||
return {}
|
||||
|
||||
def BuildManifest(self):
|
||||
ret = {
|
||||
'timestamp': int(time.time()),
|
||||
'images': [],
|
||||
}
|
||||
rollouts = self._Rollouts()
|
||||
for filename in os.listdir(self._image_dir):
|
||||
match = self._file_regex.match(filename)
|
||||
if not match:
|
||||
continue
|
||||
timestamp = int(match.group('timestamp'))
|
||||
image = {
|
||||
'timestamp': timestamp,
|
||||
'rollout_‱': rollouts.get(timestamp, 0),
|
||||
}
|
||||
with open(os.path.join(self._image_dir, filename), 'rb') as fh:
|
||||
hash_obj = hashlib.sha256()
|
||||
while True:
|
||||
data = fh.read(self._BUF_SIZE)
|
||||
if not data:
|
||||
break
|
||||
hash_obj.update(data)
|
||||
image['hash'] = hash_obj.hexdigest()
|
||||
ret['images'].append(image)
|
||||
ret['images'].sort(key=lambda x: x['timestamp'], reverse=True)
|
||||
return ret
|
||||
|
||||
|
||||
def main():
|
||||
builder = ManifestBuilder(FLAGS.image_dir, FLAGS.image_type, FLAGS.old_manifest)
|
||||
manifest = builder.BuildManifest()
|
||||
json.dump(manifest, sys.stdout, sort_keys=True, indent=4)
|
||||
sys.stdout.write('\n')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
60
server/wrap_file.py
Executable file
60
server/wrap_file.py
Executable file
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/python3
|
||||
|
||||
import argparse
|
||||
import codecs
|
||||
import json
|
||||
from OpenSSL import crypto
|
||||
import sys
|
||||
|
||||
|
||||
parser = argparse.ArgumentParser(description='iconograph wrap_file')
|
||||
parser.add_argument(
|
||||
'--cert',
|
||||
dest='cert',
|
||||
action='store',
|
||||
required=True)
|
||||
parser.add_argument(
|
||||
'--key',
|
||||
dest='key',
|
||||
action='store',
|
||||
required=True)
|
||||
parser.add_argument(
|
||||
'--other-cert',
|
||||
dest='other_certs',
|
||||
action='store',
|
||||
nargs='*')
|
||||
FLAGS = parser.parse_args()
|
||||
|
||||
|
||||
class Wrapper(object):
|
||||
|
||||
def __init__(self, key, cert, other_certs):
|
||||
with open(key, 'r') as fh:
|
||||
self._key = crypto.load_privatekey(crypto.FILETYPE_PEM, fh.read())
|
||||
with open(cert, 'r') as fh:
|
||||
self._cert_str = fh.read()
|
||||
self._cert = crypto.load_certificate(crypto.FILETYPE_PEM, self._cert_str)
|
||||
self._other_cert_strs = []
|
||||
for path in (other_certs or []):
|
||||
with open(path, 'r') as fh:
|
||||
self._other_cert_strs.append(fh.read())
|
||||
|
||||
def Wrap(self, instr):
|
||||
inbytes = instr.encode('utf8')
|
||||
return {
|
||||
'cert': self._cert_str,
|
||||
'other_certs': self._other_cert_strs,
|
||||
'sig': codecs.encode(crypto.sign(self._key, inbytes, 'sha256'), 'hex').decode('ascii'),
|
||||
'inner': instr,
|
||||
}
|
||||
|
||||
|
||||
def main():
|
||||
wrapper = Wrapper(FLAGS.key, FLAGS.cert, FLAGS.other_certs)
|
||||
wrapped = wrapper.Wrap(sys.stdin.read())
|
||||
json.dump(wrapped, sys.stdout, sort_keys=True, indent=4)
|
||||
sys.stdout.write('\n')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user