diff options
author | Mikael Magnusson <mikma@users.sourceforge.net> | 2019-09-03 21:44:59 +0000 |
---|---|---|
committer | Mikael Magnusson <mikma@users.sourceforge.net> | 2019-09-03 21:45:36 +0000 |
commit | 8df81af1ed23e1a8ccd4547827337e6a7c4cfcf9 (patch) | |
tree | ebf541494a9b43a46df4b2552c066ebfca7328c0 | |
parent | db471ef594966c1cae528159f1dfcca44a6a3229 (diff) |
Add container upgrade script
-rw-r--r-- | scripts/upgrade.py | 198 |
1 files changed, 198 insertions, 0 deletions
diff --git a/scripts/upgrade.py b/scripts/upgrade.py new file mode 100644 index 0000000..98efc70 --- /dev/null +++ b/scripts/upgrade.py @@ -0,0 +1,198 @@ +#!/usr/bin/python3 + +import sys +import time +import pylxd +from pylxd import Client + +class ExecuteError(RuntimeError): + def __init__(self, command, exit_code): + self.command = command + self.exit_code = exit_code + + def __str__(self): + return 'Command "%s" exit code %d' % (' '.join(self.command), self.exit_code) + +def find_source_image(client, image): + try: + client.images.get_by_alias(image) + return {'type': 'image', 'alias': image} + except pylxd.exceptions.NotFound as e: + if len(image) >= 12: + client.images.get(image) + return {'type': 'image', 'fingerprint': image} + else: + raise + +def copy_config(old, new): + new.devices = old.devices + new.description = old.description + new.profiles = old.profiles + new_config = new.config + + for key, value in old.config.items(): + if key.endswith("hwaddr"): + new_config[key] = value + +# for key, value in new_config.items(): +# print("%s %s" % (key, value)) + + new.config = new_config + +def log_stdout(message): + print(message, end='', flush=True) + +def log_stderr(message): + print(message, end='', file=sys.stderr, flush=True) + +class Container: + def __init__(self, container): + self.container = container + + def __getattr__(self, name): + return self.container.__getattribute__(name) + + def __setattr__(self, name, value): + if name not in ('container'): + self.container.__setattr__(name, value) + else: + super(Container, self).__setattr__(name, value) + + def execute_with_output(self, command, *args, **kwargs): + extra_args={} + if 'stderr_handler' not in kwargs: + extra_args['stderr_handler'] = log_stderr + (exit_code, stdout, stderr) = self.container.execute(command, *args, **kwargs, **extra_args) + if exit_code != 0: + raise ExecuteError(command, exit_code) + return stdout + + def execute(self, command, *args, **kwargs): + extra_args={} + if 'stdout_handler' not in kwargs: + extra_args['stdout_handler'] = log_stdout + self.execute_with_output(command, *args, **kwargs, **extra_args) + + def execute_retry(self, command, retries, *args, **kwargs): + for i in range(retries + 1): + try: + self.execute(command, *args, **kwargs) + except ExecuteError as e: + if i == retries: + raise + continue + return + raise NotImplementedError() + + def ping(self, dest): + self.execute_retry(['ping', '-c', '1', '-q', dest], 2, + stdout_handler=None) + + def sysupgrade_backup(self, ): + return self.execute_with_output(['sysupgrade', '-b', '-'], + decode=False) + + def sysupgrade_restore(self, data): + self.execute(['sysupgrade', '-r', '-'], + stdin_payload=data, decode=False) + + def opkg_list_installed(self, ): + return self.execute_with_output(['opkg', 'list-installed']) + + def opkg_update(self): + print("Update") + self.execute(['opkg', 'update']) + + def opkg_install(self, packages): + print("Installing %s" % packages) + self.execute(['opkg', 'install'] + packages) + + def package_set(self): + old_list = self.opkg_list_installed().split('\n') + old_packages = [] + i = 1 + for l in old_list: + i = i + 1 + res = l.split(' ') + if len(res) == 3: + (name, _, version) = res + old_packages.append(name) + return frozenset(old_packages) + +def usage(argv): + print("Usage:", argv[0], "<old container> <new container> <image>") + exit(1) + +def main(argv): + is_allow_existing = False + + if len(argv) == 4: + pos = 1 + else: + usage(argv) + + old_name = argv[pos]; pos=pos+1 + new_name = argv[pos]; pos=pos+1 + new_image = argv[pos]; pos=pos+1 + client = Client() + + old = Container(client.containers.get(old_name)) + + if old.status == 'Stopped': + print("Start", old_name) + old.start(wait=True) + + new_source = find_source_image(client, new_image) + new_config = {'name': new_name, 'source': new_source} + + if is_allow_existing and client.containers.exists(new_name): + new = Container(client.containers.get(new_name)) + else: + print("Create", new_name, new_config) + new = Container(client.containers.create(new_config, wait=True)) + + if new.status == 'Stopped': + print("Start", new_name) + new.start(wait=True) + + print("Ping downloads.openwrt.org") + new.ping('downloads.openwrt.org') + + print("Update package list") + new.opkg_update() + + old_set = old.package_set() + new_set = new.package_set() + add_packages = list(old_set.difference(new_set).difference(['iw'])) + + if len(add_packages) > 0: + print("Install", add_packages) + new.opkg_install(add_packages) + else: + print("No packages installed") + + print("Backup", old_name) + backup_data = old.sysupgrade_backup() + + print("Restore", new_name) + new.sysupgrade_restore(backup_data) + + print("Stop", old_name) + old.stop(wait=True) + + print("Stop", new_name) + new.stop(wait=True) + + print("Copy config") + copy_config(old, new) + new.save() + + print("Wait 2s") + time.sleep(2) + + print("Start", new_name) + new.start(wait=True) + print("Finished") + +if __name__ == '__main__': + main(sys.argv) |