summaryrefslogtreecommitdiff
path: root/morphlib
diff options
context:
space:
mode:
Diffstat (limited to 'morphlib')
-rw-r--r--morphlib/builder2.py7
-rw-r--r--morphlib/morphloader.py27
-rw-r--r--morphlib/morphloader_tests.py18
-rw-r--r--morphlib/plugins/deploy_plugin.py57
4 files changed, 98 insertions, 11 deletions
diff --git a/morphlib/builder2.py b/morphlib/builder2.py
index f8969973..3c0d9e02 100644
--- a/morphlib/builder2.py
+++ b/morphlib/builder2.py
@@ -742,8 +742,13 @@ class SystemBuilder(BuilderBase): # pragma: no cover
mount_type)
mounted.append(path)
+ # The single - is just a shell convention to fill $0 when using -c,
+ # since ordinarily $0 contains the program name.
+ # -- is used to indicate the end of options for run-parts,
+ # we don't want SYSTEM_INTEGRATION_PATH to be interpreted
+ # as an option if it happens to begin with a -
self.app.runcmd(['chroot', rootdir, 'sh', '-c',
- 'cd / && run-parts "$1"', '-', SYSTEM_INTEGRATION_PATH],
+ 'cd / && run-parts -- "$1"', '-', SYSTEM_INTEGRATION_PATH],
env=env)
except BaseException, e:
self.app.status(
diff --git a/morphlib/morphloader.py b/morphlib/morphloader.py
index ca5902a6..b5c8168d 100644
--- a/morphlib/morphloader.py
+++ b/morphlib/morphloader.py
@@ -205,6 +205,15 @@ class MultipleValidationErrors(morphlib.Error):
self.msg += ('\t' + str(error))
+class DuplicateDeploymentNameError(morphlib.Error):
+
+ def __init__(self, duplicates):
+ self.duplicates = duplicates
+ morphlib.Error.__init__(
+ self, 'Cluster morphology contains the following non-unique '
+ 'deployment names:\n%s' % '\n '.join(duplicates))
+
+
class OrderedDumper(yaml.SafeDumper):
keyorder = (
'name',
@@ -412,7 +421,23 @@ class MorphologyLoader(object):
getattr(self, '_validate_%s' % kind)(morph)
def _validate_cluster(self, morph):
- pass
+ # Deployment names must be unique within a cluster
+ deployments = collections.Counter()
+ for system in morph['systems']:
+ deployments.update(system['deploy'].iterkeys())
+ if 'subsystems' in system:
+ deployments.update(self._get_subsystem_names(system))
+ duplicates = set(deployment for deployment, count
+ in deployments.iteritems() if count > 1)
+ if duplicates:
+ raise DuplicateDeploymentNameError(duplicates)
+
+ def _get_subsystem_names(self, system): # pragma: no cover
+ for subsystem in system.get('subsystems', []):
+ for name in subsystem['deploy'].iterkeys():
+ yield name
+ for name in self._get_subsystem_names(subsystem):
+ yield name
def _validate_system(self, morph):
# A system must contain at least one stratum
diff --git a/morphlib/morphloader_tests.py b/morphlib/morphloader_tests.py
index a050e10b..82663298 100644
--- a/morphlib/morphloader_tests.py
+++ b/morphlib/morphloader_tests.py
@@ -458,6 +458,24 @@ build-system: dummy
self.loader.validate(m)
self.assertEqual(cm.exception.strata, ["foo"])
+ def test_validate_requires_unique_deployment_names_in_cluster(self):
+ subsystem = [{'morph': 'baz', 'deploy': {'foobar': None}}]
+ m = morphlib.morph3.Morphology(
+ name='cluster',
+ kind='cluster',
+ systems=[{'morph': 'foo',
+ 'deploy': {'deployment': {}},
+ 'subsystems': subsystem},
+ {'morph': 'bar',
+ 'deploy': {'deployment': {}},
+ 'subsystems': subsystem}])
+ with self.assertRaises(
+ morphlib.morphloader.DuplicateDeploymentNameError) as cm:
+ self.loader.validate(m)
+ ex = cm.exception
+ self.assertIn('foobar', ex.duplicates)
+ self.assertIn('deployment', ex.duplicates)
+
def test_loads_yaml_from_string(self):
string = '''\
name: foo
diff --git a/morphlib/plugins/deploy_plugin.py b/morphlib/plugins/deploy_plugin.py
index 3afb7b17..6fc0998c 100644
--- a/morphlib/plugins/deploy_plugin.py
+++ b/morphlib/plugins/deploy_plugin.py
@@ -35,10 +35,9 @@ class DeployPlugin(cliapp.Plugin):
'existing cluster of systems rather than do '
'an initial deployment',
group=group_deploy)
-
self.app.add_subcommand(
'deploy', self.deploy,
- arg_synopsis='CLUSTER [SYSTEM.KEY=VALUE]')
+ arg_synopsis='CLUSTER [DEPLOYMENT...] [SYSTEM.KEY=VALUE]')
def disable(self):
pass
@@ -50,6 +49,10 @@ class DeployPlugin(cliapp.Plugin):
* `CLUSTER` is the name of the cluster to deploy.
+ * `DEPLOYMENT...` is the name of zero or more deployments in the
+ morphology to deploy. If none are specified then all deployments
+ in the morphology are deployed.
+
* `SYSTEM.KEY=VALUE` can be used to assign `VALUE` to a parameter
named `KEY` for the system identified by `SYSTEM` in the cluster
morphology (see below). This will override parameters defined
@@ -249,7 +252,7 @@ class DeployPlugin(cliapp.Plugin):
Deployment configuration is stored in the deployed system as
/baserock/deployment.meta. THIS CONTAINS ALL ENVIRONMENT VARIABLES SET
- DURINGR DEPLOYMENT, so make sure you have no sensitive information in
+ DURING DEPLOYMENT, so make sure you have no sensitive information in
your environment that is being leaked. As a special case, any
environment/deployment variable that contains 'PASSWORD' in its name is
stripped out and not stored in the final system.
@@ -276,7 +279,6 @@ class DeployPlugin(cliapp.Plugin):
self.app.settings['no-git-update'] = True
cluster_name = morphlib.util.strip_morph_extension(args[0])
- env_vars = args[1:]
ws = morphlib.workspace.open('.')
sb = morphlib.sysbranchdir.open_from_within('.')
@@ -303,6 +305,22 @@ class DeployPlugin(cliapp.Plugin):
"Error: morph deploy is only supported for cluster"
" morphologies.")
+ # parse the rest of the args
+ all_subsystems = set()
+ all_deployments = set()
+ deployments = set()
+ for system in cluster_morphology['systems']:
+ all_deployments.update([sys_id for sys_id in system['deploy']])
+ if 'subsystems' in system:
+ all_subsystems.update(loader._get_subsystem_names(system))
+ for item in args[1:]:
+ if not item in all_deployments:
+ break
+ deployments.add(item)
+ env_vars = args[len(deployments) + 1:]
+ self.validate_deployment_options(
+ env_vars, all_deployments, all_subsystems)
+
bb = morphlib.buildbranch.BuildBranch(sb, build_ref_prefix,
push_temporary=False)
with contextlib.closing(bb) as bb:
@@ -336,15 +354,35 @@ class DeployPlugin(cliapp.Plugin):
self.deploy_system(build_command, deploy_tempdir,
root_repo_dir, bb.root_repo_url,
bb.root_ref, system, env_vars,
- parent_location='')
+ deployments, parent_location='')
finally:
shutil.rmtree(deploy_tempdir)
self.app.status(msg='Finished deployment')
+ def validate_deployment_options(
+ self, env_vars, all_deployments, all_subsystems):
+ for var in env_vars:
+ for subsystem in all_subsystems:
+ if subsystem == var:
+ raise cliapp.AppException(
+ 'Cannot directly deploy subsystems. Create a top '
+ 'level deployment for the subsystem %s instead.' %
+ subsystem)
+ if not any(deployment in var
+ for deployment in all_deployments) \
+ and not subsystem in var:
+ raise cliapp.AppException(
+ 'Variable referenced a non-existent deployment '
+ 'name: %s' % var)
+
def deploy_system(self, build_command, deploy_tempdir,
root_repo_dir, build_repo, ref, system, env_vars,
- parent_location):
+ deployment_filter, parent_location):
+ sys_ids = set(sys_id for sys_id, _ in system['deploy'].iteritems())
+ if deployment_filter and not \
+ any(sys_id in deployment_filter for sys_id in sys_ids):
+ return
old_status_prefix = self.app.status_prefix
system_status_prefix = '%s[%s]' % (old_status_prefix, system['morph'])
self.app.status_prefix = system_status_prefix
@@ -357,8 +395,9 @@ class DeployPlugin(cliapp.Plugin):
artifact = build_command.resolve_artifacts(srcpool)
deploy_defaults = system.get('deploy-defaults', {})
- deployments = system['deploy']
- for system_id, deploy_params in deployments.iteritems():
+ for system_id, deploy_params in system['deploy'].iteritems():
+ if not system_id in deployment_filter and deployment_filter:
+ continue
deployment_status_prefix = '%s[%s]' % (
system_status_prefix, system_id)
self.app.status_prefix = deployment_status_prefix
@@ -399,7 +438,7 @@ class DeployPlugin(cliapp.Plugin):
for subsystem in system.get('subsystems', []):
self.deploy_system(build_command, deploy_tempdir,
root_repo_dir, build_repo,
- ref, subsystem, env_vars,
+ ref, subsystem, env_vars, [],
parent_location=system_tree)
if parent_location:
deploy_location = os.path.join(parent_location,