summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMikael Magnusson <mikma@users.sourceforge.net>2019-09-03 21:44:59 +0000
committerMikael Magnusson <mikma@users.sourceforge.net>2019-09-03 21:45:36 +0000
commit8df81af1ed23e1a8ccd4547827337e6a7c4cfcf9 (patch)
treeebf541494a9b43a46df4b2552c066ebfca7328c0
parentdb471ef594966c1cae528159f1dfcca44a6a3229 (diff)
Add container upgrade script
-rw-r--r--scripts/upgrade.py198
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)