address namespacing

This commit is contained in:
Scott Moser 2015-03-27 14:19:56 -04:00
parent 68516e6ce8
commit ff9b3122ff
2 changed files with 88 additions and 10 deletions

View File

@ -7,7 +7,7 @@ Example config:
snappy:
system_snappy: auto
ssh_enabled: False
packages: [etcd, pkg2]
packages: [etcd, pkg2.smoser]
config:
pkgname:
key2: value2
@ -18,11 +18,15 @@ Example config:
- ssh_enabled:
This defaults to 'False'. Set to a non-false value to enable ssh service
- snap installation and config
The above would install 'etcd', and then install 'pkg2' with a
The above would install 'etcd', and then install 'pkg2.smoser' with a
'--config=<file>' argument where 'file' as 'config-blob' inside it.
If 'pkgname' is installed already, then 'snappy config pkgname <file>'
will be called where 'file' has 'pkgname-config-blob' as its content.
Entries in 'config' can be namespaced or non-namespaced for a package.
In either case, the config provided to snappy command is non-namespaced.
The package name is provided as it appears.
If 'packages_dir' has files in it that end in '.snap', then they are
installed. Given 3 files:
<packages_dir>/foo.snap
@ -52,6 +56,7 @@ LOG = logging.getLogger(__name__)
frequency = PER_INSTANCE
SNAPPY_CMD = "snappy"
NAMESPACE_DELIM = '.'
BUILTIN_CFG = {
'packages': [],
@ -81,10 +86,20 @@ def makeop(op, name, config=None, path=None, cfgfile=None):
'cfgfile': cfgfile})
def get_package_config(configs, name):
# load the package's config from the configs dict.
# prefer full-name entry (config-example.canonical)
# over short name entry (config-example)
if name in configs:
return configs[name]
return configs.get(name.partition(NAMESPACE_DELIM)[0])
def get_package_ops(packages, configs, installed=None, fspath=None):
# get the install an config operations that should be done
if installed is None:
installed = read_installed_packages()
short_installed = [p.partition(NAMESPACE_DELIM)[0] for p in installed]
if not packages:
packages = []
@ -95,21 +110,31 @@ def get_package_ops(packages, configs, installed=None, fspath=None):
ops += get_fs_package_ops(fspath)
for name in packages:
ops.append(makeop('install', name, configs.get('name')))
ops.append(makeop('install', name, get_package_config(configs, name)))
to_install = [f['name'] for f in ops]
short_to_install = [f['name'].partition(NAMESPACE_DELIM)[0] for f in ops]
for name in configs:
if name in installed and name not in to_install:
ops.append(makeop('config', name, config=configs[name]))
if name in to_install:
continue
shortname = name.partition(NAMESPACE_DELIM)[0]
if shortname in short_to_install:
continue
if name in installed or shortname in short_installed:
ops.append(makeop('config', name,
config=get_package_config(configs, name)))
# prefer config entries to filepath entries
for op in ops:
if op['op'] != 'install' or not op['cfgfile']:
continue
name = op['name']
if name in configs and op['op'] == 'install' and 'cfgfile' in op:
LOG.debug("preferring configs[%s] over '%s'", name, op['cfgfile'])
fromcfg = get_package_config(configs, op['name'])
if fromcfg:
LOG.debug("preferring configs[%(name)s] over '%(cfgfile)s'", op)
op['cfgfile'] = None
op['config'] = configs[op['name']]
op['config'] = fromcfg
return ops
@ -118,6 +143,7 @@ def render_snap_op(op, name, path=None, cfgfile=None, config=None):
if op not in ('install', 'config'):
raise ValueError("cannot render op '%s'" % op)
shortname = name.partition(NAMESPACE_DELIM)[0]
try:
cfg_tmpf = None
if config is not None:
@ -126,7 +152,7 @@ def render_snap_op(op, name, path=None, cfgfile=None, config=None):
# packagename:
# config
# Note, however, we do not touch config files on disk.
nested_cfg = {'config': {name: config}}
nested_cfg = {'config': {shortname: config}}
(fd, cfg_tmpf) = tempfile.mkstemp()
os.write(fd, util.yaml_dumps(nested_cfg).encode())
os.close(fd)
@ -151,7 +177,13 @@ def render_snap_op(op, name, path=None, cfgfile=None, config=None):
def read_installed_packages():
return [p[0] for p in read_pkg_data()]
ret = []
for (name, date, version, dev) in read_pkg_data():
if dev:
ret.append(NAMESPACE_DELIM.join(name, dev))
else:
ret.append(name)
return ret
def read_pkg_data():

View File

@ -90,6 +90,15 @@ class TestInstallPackages(t_help.TestCase):
makeop('install', 'pkg2', b'mycfg2'),
makeop('config', 'xinstalled', b'xcfg')])
def test_package_ops_install_long_config_short(self):
# a package can be installed by full name, but have config by short
cfg = {'k1': 'k2'}
ret = get_package_ops(
packages=['config-example.canonical'],
configs={'config-example': cfg}, installed=[])
self.assertEqual(
ret, [makeop('install', 'config-example.canonical', cfg)])
def test_package_ops_with_file(self):
self.populate_tmp(
{"snapf1.snap": b"foo1", "snapf1.config": b"snapf1cfg",
@ -114,6 +123,34 @@ class TestInstallPackages(t_help.TestCase):
ret, [makeop_tmpd(self.tmp, 'install', 'snapf1',
path="snapf1.snap", config="snapf1cfg-config")])
def test_package_ops_namespacing(self):
cfgs = {
'config-example': {'k1': 'v1'},
'pkg1': {'p1': 'p2'},
'ubuntu-core': {'c1': 'c2'},
'notinstalled.smoser': {'s1': 's2'},
}
cfg = {'config-example-k1': 'config-example-k2'}
ret = get_package_ops(
packages=['config-example.canonical'], configs=cfgs,
installed=['config-example.smoser', 'pkg1.canonical',
'ubuntu-core'])
expected_configs = [
makeop('config', 'pkg1', config=cfgs['pkg1']),
makeop('config', 'ubuntu-core', config=cfgs['ubuntu-core'])]
expected_installs = [
makeop('install', 'config-example.canonical',
config=cfgs['config-example'])]
installs = [i for i in ret if i['op'] == 'install']
configs = [c for c in ret if c['op'] == 'config']
self.assertEqual(installs, expected_installs)
# configs are not ordered
self.assertEqual(len(configs), len(expected_configs))
self.assertTrue(all(found in expected_configs for found in configs))
def test_render_op_localsnap(self):
self.populate_tmp({"snapf1.snap": b"foo1"})
op = makeop_tmpd(self.tmp, 'install', 'snapf1',
@ -190,6 +227,15 @@ class TestInstallPackages(t_help.TestCase):
data_found = self.snapcmds[0][2]
self.assertEqual(mycfg, data_found['config'][name])
def test_render_long_configs_short(self):
# install a namespaced package should have un-namespaced config
mycfg = {'k1': 'k2'}
name = 'snapf1'
op = makeop('install', name + ".smoser", config=mycfg)
render_snap_op(**op)
data_found = self.snapcmds[0][2]
self.assertEqual(mycfg, data_found['config'][name])
def test_render_does_not_pad_cfgfile(self):
# package_ops with cfgfile should not modify --file= content.
mydata = "foo1: bar1\nk: [l1, l2, l3]\n"