summaryrefslogtreecommitdiff
path: root/morphlib/plugins
diff options
context:
space:
mode:
Diffstat (limited to 'morphlib/plugins')
-rw-r--r--morphlib/plugins/branch_and_merge_new_plugin.py156
-rw-r--r--morphlib/plugins/copy-artifacts_plugin.py142
-rw-r--r--morphlib/plugins/deploy_plugin.py1
-rw-r--r--morphlib/plugins/gc_plugin.py7
-rw-r--r--morphlib/plugins/list_artifacts_plugin.py124
5 files changed, 179 insertions, 251 deletions
diff --git a/morphlib/plugins/branch_and_merge_new_plugin.py b/morphlib/plugins/branch_and_merge_new_plugin.py
index 51cba401..5ac8353a 100644
--- a/morphlib/plugins/branch_and_merge_new_plugin.py
+++ b/morphlib/plugins/branch_and_merge_new_plugin.py
@@ -363,124 +363,32 @@ class SimpleBranchAndMergePlugin(cliapp.Plugin):
Command line arguments:
- * `SYSTEM` is the name of a system morphology in the root repository
- of the current system branch.
- * `STRATUM` is the name of a stratum inside the system.
- * `CHUNK` is the name of a chunk inside the stratum.
+ * `CHUNK` is the name of a chunk
- This marks the specified stratum or chunk (if given) as being
- changed within the system branch, by creating the git branches in
- the affected repositories, and changing the relevant morphologies
- to point at those branches. It also creates a local clone of
- the git repository of the stratum or chunk.
-
- For example:
-
- morph edit devel-system-x86-64-generic devel
-
- The above command will mark the `devel` stratum as being
- modified in the current system branch. In this case, the stratum's
- morphology is in the same git repository as the system morphology,
- so there is no need to create a new git branch. However, the
- system morphology is modified to point at the stratum morphology
- in the same git branch, rather than the original branch.
-
- In other words, where the system morphology used to say this:
-
- morph: devel
- repo: baserock:baserock/morphs
- ref: master
-
- The updated system morphology will now say this instead:
-
- morph: devel
- repo: baserock:baserock/morphs
- ref: jrandom/new-feature
-
- (Assuming the system branch is called `jrandom/new-feature`.)
-
- Another example:
-
- morph edit devel-system-x86_64-generic devel gcc
-
- The above command will mark the `gcc` chunk as being edited in
- the current system branch. Morph will clone the `gcc` repository
- locally, into the current workspace, and create a new (local)
- branch named after the system branch. It will also change the
- stratum morphology to refer to the new git branch, instead of
- whatever branch it was referring to originally.
-
- If the `gcc` repository already had a git branch named after
- the system branch, that is reused. Similarly, if the stratum
- morphology was already pointing that that branch, it doesn't
- need to be changed again. In that case, the only action Morph
- does is to clone the chunk repository locally, and if that was
- also done already, Morph does nothing.
+ This makes a local checkout of CHUNK in the current system branch
+ and edits any stratum morphology file(s) containing the chunk
'''
- if len(args) not in (2, 3):
- raise cliapp.AppException('morph edit needs the names of a system,'
- ' a stratum and optionally a chunk'
- ' as parameters')
-
- system_name = morphlib.util.strip_morph_extension(args[0])
- stratum_name = morphlib.util.strip_morph_extension(args[1])
- chunk_name = None
- if len(args) == 3:
- chunk_name = morphlib.util.strip_morph_extension(args[2])
+ if len(args) != 1:
+ raise cliapp.AppException('morph edit needs a chunk '
+ 'as parameter')
ws = morphlib.workspace.open('.')
sb = morphlib.sysbranchdir.open_from_within('.')
loader = morphlib.morphloader.MorphologyLoader()
+ morphs = self._load_all_sysbranch_morphologies(sb, loader)
- # Load the system morphology, and all stratum morphologies, including
- # all the strata that are being build-depended on.
-
- logging.debug('Loading system morphology')
- system_morph = loader.load_from_file(
- sb.get_filename(sb.root_repository_url, system_name + '.morph'))
- if system_morph['kind'] != 'system':
- raise cliapp.AppException("%s is not a system" % system_name)
- system_morph.repo_url = sb.root_repository_url
- system_morph.ref = sb.system_branch_name
- system_morph.filename = system_name + '.morph'
-
- logging.debug('Loading stratum morphologies')
- morphs = self._load_stratum_morphologies(loader, sb, system_morph)
- morphs.add_morphology(system_morph)
- logging.debug('morphs: %s' % repr(morphs.morphologies))
-
- # Change refs to the stratum to be to the system branch.
- # Note: this currently only supports strata in root repository.
-
- logging.debug('Changing refs to stratum %s' % stratum_name)
- stratum_morph = morphs.get_stratum_in_system(
- system_morph, stratum_name)
- morphs.change_ref(
- stratum_morph.repo_url, stratum_morph.ref, stratum_morph.filename,
- sb.system_branch_name)
- logging.debug('morphs: %s' % repr(morphs.morphologies))
-
- # If we're editing a chunk, make it available locally, with the
- # relevant git branch checked out. This also invents the new branch
- # name.
-
- if chunk_name:
- logging.debug('Editing chunk %s' % chunk_name)
-
- chunk_url, chunk_ref, chunk_morph = morphs.get_chunk_triplet(
- stratum_morph, chunk_name)
+ def edit_chunk(morph, chunk_name):
+ chunk_url, chunk_ref, chunk_morph = (
+ morphs.get_chunk_triplet(morph, chunk_name))
chunk_dirname = sb.get_git_directory_name(chunk_url)
+
if not os.path.exists(chunk_dirname):
lrc, rrc = morphlib.util.new_repo_caches(self.app)
cached_repo = lrc.get_updated_repo(chunk_url)
- # FIXME: This makes the simplifying assumption that
- # a chunk branch must have the same name as the system
- # branch.
-
gd = sb.clone_cached_repo(cached_repo, chunk_ref)
if chunk_ref != sb.system_branch_name:
gd.branch(sb.system_branch_name, chunk_ref)
@@ -491,16 +399,48 @@ class SimpleBranchAndMergePlugin(cliapp.Plugin):
gd.fat_init()
gd.fat_pull()
- # Change the refs to the chunk.
- if chunk_ref != sb.system_branch_name:
- morphs.change_ref(
- chunk_url, chunk_ref, chunk_morph + '.morph',
- sb.system_branch_name)
+ # Change the refs to the chunk.
+ if chunk_ref != sb.system_branch_name:
+ morphs.change_ref(
+ chunk_url, chunk_ref, chunk_morph + '.morph',
+ sb.system_branch_name)
+
+ return chunk_dirname
+
+ chunk_name = morphlib.util.strip_morph_extension(args[0])
+ dirs = set()
+ found = 0
+
+ for morph in morphs.morphologies:
+ if morph['kind'] == 'stratum':
+ for chunk in morph['chunks']:
+ if chunk['name'] == chunk_name:
+ self.app.status(
+ msg='Editing %(chunk)s in %(stratum)s stratum',
+ chunk=chunk_name, stratum=morph['name'])
+ chunk_dirname = edit_chunk(morph, chunk_name)
+ dirs.add(chunk_dirname)
+ found = found + 1
# Save any modified strata.
self._save_dirty_morphologies(loader, sb, morphs.morphologies)
+ if found == 0:
+ self.app.status(
+ msg="No chunk %(chunk)s found. If you want to create one, add "
+ "an entry to a stratum morph file.", chunk=chunk_name)
+
+ if found >= 1:
+ dirs_list = ', '.join(sorted(dirs))
+ self.app.status(
+ msg="Chunk %(chunk)s source is available at %(dirs)s",
+ chunk=chunk_name, dirs=dirs_list)
+
+ if found > 1:
+ self.app.status(
+ msg="Notice that this chunk appears in more than one stratum")
+
def show_system_branch(self, args):
'''Show the name of the current system branch.'''
diff --git a/morphlib/plugins/copy-artifacts_plugin.py b/morphlib/plugins/copy-artifacts_plugin.py
deleted file mode 100644
index 577d0ef2..00000000
--- a/morphlib/plugins/copy-artifacts_plugin.py
+++ /dev/null
@@ -1,142 +0,0 @@
-# Copyright (C) 2013 Codethink Limited
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; version 2 of the License.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License along
-# with this program; if not, write to the Free Software Foundation, Inc.,
-# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-
-
-
-# FIXME: no tests
-
-
-
-import os
-import glob
-import json
-
-
-import cliapp
-
-
-
-import morphlib
-
-
-
-class CopyArtifactsPlugin(cliapp.Plugin):
-
- def enable(self):
- self.app.add_subcommand(
- 'list-artifacts', self.list_artifacts,
- arg_synopsis='SYSTEM-ARTIFACT')
- self.app.add_subcommand(
- 'copy-artifacts', self.copy_artifacts,
- arg_synopsis='SYSTEM-ARTIFACT DESTINATION')
-
- def disable(self):
- pass
-
- def list_artifacts(self, args):
- '''List every artifact that makes up a system artifact.
-
- Command line arguments:
-
- * `SYSTEM-ARTIFACT` is the filename for a build artifact for
- a system (ending in `-rootfs`).
-
- list-artifacts reads the system artifact and writes out a list
- of the filenames, in the local artifact cache, of all the
- component artifacts included in the system. It does not include
- build-dependencies of the system, unless they're included in
- the system.
-
- '''
-
- if len(args) != 1:
- raise cliapp.AppException(
- 'Wrong number of arguments to list-artifacts command'
- '(see help)')
-
- system = args[0]
- artifacts = self.find_artifacts(system)
- for artifact in artifacts:
- self.app.output.write(artifact + "\n")
-
- def copy_artifacts(self, args):
- '''Copy every artifact that makes up a system to an rsync path.
-
- Command line arguments:
-
- * `SYSTEM-ARTIFACT` is the filename for a build artifact for
- a system (ending in `-rootfs`).
- * `DESTINATION` is a URL for where the artifacts are to be
- copied to, in a form suitable for the rsync program.
-
- This command is useful for copying artifacts from a local
- system to a Morph artifact cache server, so they can be
- shared by other people. It can also be used to archive
- artifacts used for a release. Note, however, that it does
- not include artifacts that are build-dependencies of the
- strata included in the system, but not actually in cluded
- in the system.
-
- Example:
-
- morph copy-artifacts /src/cache/artifacts/*.system.* \
- cache.example.com:/srv/cache/
-
- '''
-
- if len(args) != 2:
- raise cliapp.AppException(
- 'Wrong number of arguments to copy-artifacts command'
- '(see help)')
-
- system = args[0]
- rsync_dest = args[1]
- artifacts = self.find_artifacts(system)
- cmdline = ['rsync']
- cmdline.extend(artifacts)
- cmdline.append(rsync_dest)
- cliapp.runcmd(cmdline)
-
- def find_artifacts(self, system):
- artifacts_dir = os.path.join(
- self.app.settings['cachedir'], 'artifacts')
- artifacts = []
-
- def find_in_system(dirname):
- metadirs = [
- os.path.join(dirname, 'factory', 'baserock'),
- os.path.join(dirname, 'baserock')
- ]
- existing_metadirs = [x for x in metadirs if os.path.isdir(x)]
- if not existing_metadirs:
- raise NotASystemArtifactError(system)
- metadir = existing_metadirs[0]
- for basename in glob.glob(os.path.join(metadir, '*.meta')):
- metafile = os.path.join(metadir, basename)
- metadata = json.load(open(metafile))
- cache_key = metadata['cache-key']
- artifact_glob = os.path.join(artifacts_dir, cache_key) + '*'
- found_artifacts = glob.glob(artifact_glob)
- if not found_artifacts:
- raise cliapp.AppException('Could not find cache-key '
- + cache_key + 'for artifact '
- + metajson['artifact-name'])
- artifacts.extend(found_artifacts)
-
- morphlib.bins.call_in_artifact_directory(
- self.app, system, find_in_system)
-
- return artifacts
-
diff --git a/morphlib/plugins/deploy_plugin.py b/morphlib/plugins/deploy_plugin.py
index 1d582949..3afb7b17 100644
--- a/morphlib/plugins/deploy_plugin.py
+++ b/morphlib/plugins/deploy_plugin.py
@@ -274,6 +274,7 @@ class DeployPlugin(cliapp.Plugin):
self.app.settings['tempdir-min-space'],
'/', 0)
+ self.app.settings['no-git-update'] = True
cluster_name = morphlib.util.strip_morph_extension(args[0])
env_vars = args[1:]
diff --git a/morphlib/plugins/gc_plugin.py b/morphlib/plugins/gc_plugin.py
index df736afe..43de173e 100644
--- a/morphlib/plugins/gc_plugin.py
+++ b/morphlib/plugins/gc_plugin.py
@@ -61,6 +61,11 @@ class GCPlugin(cliapp.Plugin):
It also removes any left over temporary chunks and staging areas
from failed builds.
+ In addition we remove failed deployments, generally these are
+ cleared up by morph during deployment but in some cases they
+ won't be e.g. if morph gets a SIGKILL or the machine running
+ morph loses power.
+
'''
tempdir = self.app.settings['tempdir']
@@ -76,7 +81,7 @@ class GCPlugin(cliapp.Plugin):
def cleanup_tempdir(self, temp_path, min_space):
self.app.status(msg='Cleaning up temp dir %(temp_path)s',
temp_path=temp_path, chatty=True)
- for subdir in ('failed', 'chunks'):
+ for subdir in ('deployments', 'failed', 'chunks'):
if morphlib.util.get_bytes_free_in_path(temp_path) >= min_space:
self.app.status(msg='Not Removing subdirectory '
'%(subdir)s, enough space already cleared',
diff --git a/morphlib/plugins/list_artifacts_plugin.py b/morphlib/plugins/list_artifacts_plugin.py
new file mode 100644
index 00000000..5e64f708
--- /dev/null
+++ b/morphlib/plugins/list_artifacts_plugin.py
@@ -0,0 +1,124 @@
+# Copyright (C) 2014 Codethink Limited
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+# This plugin is used as part of the Baserock automated release process.
+#
+# See: <http://wiki.baserock.org/guides/release-process> for more information.
+
+
+import cliapp
+import morphlib
+
+
+class ListArtifactsPlugin(cliapp.Plugin):
+
+ def enable(self):
+ self.app.add_subcommand(
+ 'list-artifacts', self.list_artifacts,
+ arg_synopsis='REPO REF MORPH [MORPH]...')
+
+ def disable(self):
+ pass
+
+ def list_artifacts(self, args):
+ '''List every artifact in the build graph of a system.
+
+ Command line arguments:
+
+ * `REPO` is a git repository URL.
+ * `REF` is a branch or other commit reference in that repository.
+ * `MORPH` is a system morphology name at that ref.
+
+ You can pass multiple values for `MORPH`, in which case the command
+ outputs the union of the build graphs of all the systems passed in.
+
+ The output includes any meta-artifacts such as .meta and .build-log
+ files.
+
+ '''
+
+ if len(args) < 3:
+ raise cliapp.AppException(
+ 'Wrong number of arguments to list-artifacts command '
+ '(see help)')
+
+ repo, ref = args[0], args[1]
+ system_names = map(morphlib.util.strip_morph_extension, args[2:])
+
+ self.lrc, self.rrc = morphlib.util.new_repo_caches(self.app)
+ self.resolver = morphlib.artifactresolver.ArtifactResolver()
+
+ artifact_files = set()
+ for system_name in system_names:
+ system_artifact_files = self.list_artifacts_for_system(
+ repo, ref, system_name)
+ artifact_files.update(system_artifact_files)
+
+ for artifact_file in sorted(artifact_files):
+ print artifact_file
+
+ def list_artifacts_for_system(self, repo, ref, system_name):
+ '''List all artifact files in the build graph of a single system.'''
+
+ # Sadly, we must use a fresh source pool and a fresh list of artifacts
+ # for each system. Creating a source pool is slow (queries every Git
+ # repo involved in the build) and resolving artifacts isn't so quick
+ # either. Unfortunately, each Source object can only have one set of
+ # Artifact objects associated, which means the source pool cannot mix
+ # sources that are being built for multiple architectures: the build
+ # graph representation does not distinguish chunks or strata of
+ # different architectures right now.
+
+ self.app.status(
+ msg='Creating source pool for %s' % system_name, chatty=True)
+ source_pool = self.app.create_source_pool(
+ self.lrc, self.rrc, (repo, ref, system_name + '.morph'))
+
+ self.app.status(
+ msg='Resolving artifacts for %s' % system_name, chatty=True)
+ artifacts = self.resolver.resolve_artifacts(source_pool)
+
+ def find_artifact_by_name(artifacts_list, name):
+ for a in artifacts_list:
+ if a.source.filename == name + '.morph':
+ return a
+ raise ValueError
+
+ system_artifact = find_artifact_by_name(artifacts, system_name)
+
+ self.app.status(
+ msg='Computing cache keys for %s' % system_name, chatty=True)
+ build_env = morphlib.buildenvironment.BuildEnvironment(
+ self.app.settings, system_artifact.source.morphology['arch'])
+ ckc = morphlib.cachekeycomputer.CacheKeyComputer(build_env)
+
+ artifact_files = set()
+ for artifact in system_artifact.walk():
+ artifact.cache_key = ckc.compute_key(artifact)
+ artifact.cache_id = ckc.get_cache_id(artifact)
+
+ artifact_files.add(artifact.basename())
+
+ if artifact.source.morphology.needs_artifact_metadata_cached:
+ artifact_files.add('%s.meta' % artifact.basename())
+
+ # 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)
+ if artifact.source.morphology['kind'] == 'chunk':
+ artifact_files.add('%s.build-log' % artifact.cache_key)
+
+ return artifact_files