diff options
-rw-r--r-- | morphlib/artifact.py | 3 | ||||
-rwxr-xr-x | morphlib/exts/ssh-rsync.write | 182 | ||||
-rw-r--r-- | morphlib/plugins/list_artifacts_plugin.py | 13 |
3 files changed, 112 insertions, 86 deletions
diff --git a/morphlib/artifact.py b/morphlib/artifact.py index 8b4ce65e..7a40a81a 100644 --- a/morphlib/artifact.py +++ b/morphlib/artifact.py @@ -24,9 +24,6 @@ class Artifact(object): * ``name`` -- the name of the artifact * ``dependents`` -- list of Sources that need this Artifact to be built - The ``dependencies`` and ``dependents`` lists MUST be modified by - the ``add_dependencies`` and ``add_dependent`` methods only. - ''' def __init__(self, source, name): diff --git a/morphlib/exts/ssh-rsync.write b/morphlib/exts/ssh-rsync.write index 775619ec..0ce89c7f 100755 --- a/morphlib/exts/ssh-rsync.write +++ b/morphlib/exts/ssh-rsync.write @@ -18,6 +18,7 @@ '''A Morph deployment write extension for upgrading systems over ssh.''' +import contextlib import cliapp import os import sys @@ -45,107 +46,134 @@ class SshRsyncWriteExtension(morphlib.writeexts.WriteExtension): ''' - def process_args(self, args): - if len(args) != 2: - raise cliapp.AppException('Wrong number of command line args') - - temp_root, location = args - - self.upgrade_remote_system(location, temp_root) - - def upgrade_remote_system(self, location, temp_root): - root_disk = self.find_root_disk(location) - uuid = cliapp.ssh_runcmd(location, ['blkid', '-s', 'UUID', '-o', - 'value', root_disk]).strip() - - self.complete_fstab_for_btrfs_layout(temp_root, uuid) + def find_root_disk(self, location): + '''Read /proc/mounts on location to find which device contains "/"''' - version_label = os.environ['VERSION_LABEL'] - autostart = self.get_environment_boolean('AUTOSTART') + self.status(msg='Finding device that contains "/"') + contents = cliapp.ssh_runcmd(location, ['cat', '/proc/mounts']) + for line in contents.splitlines(): + line_words = line.split() + if (line_words[1] == '/' and line_words[0] != 'rootfs'): + return line_words[0] + @contextlib.contextmanager + def _remote_mount_point(self, location): self.status(msg='Creating remote mount point') remote_mnt = cliapp.ssh_runcmd(location, ['mktemp', '-d']).strip() try: - self.status(msg='Mounting root disk') - cliapp.ssh_runcmd(location, ['mount', root_disk, remote_mnt]) - except Exception as e: - ssh_runcmd_ignore_failure(location, ['rmdir', remote_mnt]) - raise e + yield remote_mnt + finally: + self.status(msg='Removing remote mount point') + cliapp.ssh_runcmd(location, ['rmdir', remote_mnt]) + @contextlib.contextmanager + def _remote_mount(self, location, root_disk, mountpoint): + self.status(msg='Mounting root disk') + cliapp.ssh_runcmd(location, ['mount', root_disk, mountpoint]) try: - version_root = os.path.join(remote_mnt, 'systems', version_label) - orig_dir = os.path.join(version_root, 'orig') - - self.status(msg='Creating %s' % version_root) - cliapp.ssh_runcmd(location, ['mkdir', version_root]) - - self.create_remote_orig(location, version_root, remote_mnt, - temp_root) - - # Use the system-version-manager from the new system we just - # installed, so that we can upgrade from systems that don't have - # it installed. - self.status(msg='Calling system-version-manager to deploy upgrade') - deployment = os.path.join('/systems', version_label, 'orig') - system_config_sync = os.path.join( - remote_mnt, 'systems', version_label, 'orig', 'usr', 'bin', - 'baserock-system-config-sync') - system_version_manager = os.path.join( - remote_mnt, 'systems', version_label, 'orig', 'usr', 'bin', - 'system-version-manager') - cliapp.ssh_runcmd(location, - ['env', 'BASEROCK_SYSTEM_CONFIG_SYNC='+system_config_sync, - system_version_manager, 'deploy', deployment]) - - self.status(msg='Setting %s as the new default system' % - version_label) - cliapp.ssh_runcmd(location, - [system_version_manager, 'set-default', version_label]) - except Exception as e: - self.status(msg='Deployment failed') - ssh_runcmd_ignore_failure( - location, ['btrfs', 'subvolume', 'delete', orig_dir]) - ssh_runcmd_ignore_failure( - location, ['rm', '-rf', version_root]) - raise e + yield finally: - self.status(msg='Removing temporary mounts') - cliapp.ssh_runcmd(location, ['umount', remote_mnt]) - cliapp.ssh_runcmd(location, ['rmdir', remote_mnt]) + self.status(msg='Unmounting root disk') + cliapp.ssh_runcmd(location, ['umount', mountpoint]) + + @contextlib.contextmanager + def _created_version_root(self, location, remote_mnt, version_label): + version_root = os.path.join(remote_mnt, 'systems', version_label) + self.status(msg='Creating %(root)s', root=version_root) + cliapp.ssh_runcmd(location, ['mkdir', version_root]) + try: + yield version_root + except BaseException as e: + # catch all, we always want to clean up + self.status(msg='Cleaning up %(root)s', root=version_root) + ssh_runcmd_ignore_failure(location, ['rmdir', version_root]) + raise - if autostart: - self.status(msg="Rebooting into new system ...") - ssh_runcmd_ignore_failure(location, ['reboot']) + def get_old_orig(self, location, remote_mnt): + '''Identify which subvolume to snapshot from''' - def create_remote_orig(self, location, version_root, remote_mnt, - temp_root): - '''Create the subvolume version_root/orig on location''' + # rawdisk upgrades use 'factory' + return os.path.join(remote_mnt, 'systems', 'factory', 'orig') + @contextlib.contextmanager + def _created_orig_subvolume(self, location, remote_mnt, version_root): self.status(msg='Creating "orig" subvolume') old_orig = self.get_old_orig(location, remote_mnt) new_orig = os.path.join(version_root, 'orig') cliapp.ssh_runcmd(location, ['btrfs', 'subvolume', 'snapshot', old_orig, new_orig]) + try: + yield new_orig + except BaseException as e: + ssh_runcmd_ignore_failure( + location, ['btrfs', 'subvolume', 'delete', new_orig]) + raise + def populate_remote_orig(self, location, new_orig, temp_root): + '''Populate the subvolume version_root/orig on location''' + + self.status(msg='Populating "orig" subvolume') cliapp.runcmd(['rsync', '-as', '--checksum', '--numeric-ids', '--delete', temp_root + os.path.sep, '%s:%s' % (location, new_orig)]) - def get_old_orig(self, location, remote_mnt): - '''Identify which subvolume to snapshot from''' + @contextlib.contextmanager + def _deployed_version(self, location, version_label, + system_config_sync, system_version_manager): + self.status(msg='Calling system-version-manager to deploy upgrade') + deployment = os.path.join('/systems', version_label, 'orig') + cliapp.ssh_runcmd(location, + ['env', 'BASEROCK_SYSTEM_CONFIG_SYNC='+system_config_sync, + system_version_manager, 'deploy', deployment]) + try: + yield deployment + except BaseException as e: + self.status(msg='Cleaning up failed version installation') + cliapp.ssh_runcmd(location, + [system_version_manager, 'remove', version_label]) + raise - # rawdisk upgrades use 'factory' - return os.path.join(remote_mnt, 'systems', 'factory', 'orig') + def upgrade_remote_system(self, location, temp_root): + root_disk = self.find_root_disk(location) + uuid = cliapp.ssh_runcmd(location, ['blkid', '-s', 'UUID', '-o', + 'value', root_disk]).strip() - def find_root_disk(self, location): - '''Read /proc/mounts on location to find which device contains "/"''' + self.complete_fstab_for_btrfs_layout(temp_root, uuid) - self.status(msg='Finding device that contains "/"') - contents = cliapp.ssh_runcmd(location, ['cat', '/proc/mounts']) - for line in contents.splitlines(): - line_words = line.split() - if (line_words[1] == '/' and line_words[0] != 'rootfs'): - return line_words[0] + version_label = os.environ['VERSION_LABEL'] + autostart = self.get_environment_boolean('AUTOSTART') + + with self._remote_mount_point(location) as remote_mnt, \ + self._remote_mount(location, root_disk, remote_mnt), \ + self._created_version_root(location, remote_mnt, + version_label) as version_root, \ + self._created_orig_subvolume(location, remote_mnt, + version_root) as orig: + self.populate_remote_orig(location, orig, temp_root) + system_root = os.path.join(remote_mnt, 'systems', + version_label, 'orig') + config_sync = os.path.join(system_root, 'usr', 'bin', + 'baserock-system-config-sync') + version_manager = os.path.join(system_root, 'usr', 'bin', + 'system-version-manager') + with self._deployed_version(location, version_label, + config_sync, version_manager): + self.status(msg='Setting %(v)s as the new default system', + v=version_label) + cliapp.ssh_runcmd(location, [version_manager, + 'set-default', version_label]) + + if autostart: + self.status(msg="Rebooting into new system ...") + ssh_runcmd_ignore_failure(location, ['reboot']) + + def process_args(self, args): + if len(args) != 2: + raise cliapp.AppException('Wrong number of command line args') + + temp_root, location = args + + self.upgrade_remote_system(location, temp_root) SshRsyncWriteExtension().run() diff --git a/morphlib/plugins/list_artifacts_plugin.py b/morphlib/plugins/list_artifacts_plugin.py index 61c8d160..0f14a579 100644 --- a/morphlib/plugins/list_artifacts_plugin.py +++ b/morphlib/plugins/list_artifacts_plugin.py @@ -89,7 +89,7 @@ class ListArtifactsPlugin(cliapp.Plugin): self.app.status( msg='Resolving artifacts for %s' % system_filename, chatty=True) - artifacts = self.resolver.resolve_artifacts(source_pool) + root_artifacts = self.resolver.resolve_root_artifacts(source_pool) def find_artifact_by_name(artifacts_list, filename): for a in artifacts_list: @@ -97,7 +97,8 @@ class ListArtifactsPlugin(cliapp.Plugin): return a raise ValueError - system_artifact = find_artifact_by_name(artifacts, system_filename) + system_artifact = find_artifact_by_name(root_artifacts, + system_filename) self.app.status( msg='Computing cache keys for %s' % system_filename, chatty=True) @@ -106,8 +107,8 @@ class ListArtifactsPlugin(cliapp.Plugin): ckc = morphlib.cachekeycomputer.CacheKeyComputer(build_env) for source in set(a.source for a in system_artifact.walk()): - artifact.cache_key = ckc.compute_key(artifact) - artifact.cache_id = ckc.get_cache_id(artifact) + source.cache_key = ckc.compute_key(source) + source.cache_id = ckc.get_cache_id(source) artifact_files = set() for artifact in system_artifact.walk(): @@ -120,8 +121,8 @@ class ListArtifactsPlugin(cliapp.Plugin): # This is unfortunate hardwiring of behaviour; in future we # should list all artifacts in the meta-artifact file, so we # don't have to guess what files there will be. - artifact_files.add('%s.meta' % artifact.cache_key) + artifact_files.add('%s.meta' % artifact.source.cache_key) if artifact.source.morphology['kind'] == 'chunk': - artifact_files.add('%s.build-log' % artifact.cache_key) + artifact_files.add('%s.build-log' % artifact.source.cache_key) return artifact_files |