address namespacing
This commit is contained in:
parent
68516e6ce8
commit
ff9b3122ff
@ -7,7 +7,7 @@ Example config:
|
|||||||
snappy:
|
snappy:
|
||||||
system_snappy: auto
|
system_snappy: auto
|
||||||
ssh_enabled: False
|
ssh_enabled: False
|
||||||
packages: [etcd, pkg2]
|
packages: [etcd, pkg2.smoser]
|
||||||
config:
|
config:
|
||||||
pkgname:
|
pkgname:
|
||||||
key2: value2
|
key2: value2
|
||||||
@ -18,11 +18,15 @@ Example config:
|
|||||||
- ssh_enabled:
|
- ssh_enabled:
|
||||||
This defaults to 'False'. Set to a non-false value to enable ssh service
|
This defaults to 'False'. Set to a non-false value to enable ssh service
|
||||||
- snap installation and config
|
- 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.
|
'--config=<file>' argument where 'file' as 'config-blob' inside it.
|
||||||
If 'pkgname' is installed already, then 'snappy config pkgname <file>'
|
If 'pkgname' is installed already, then 'snappy config pkgname <file>'
|
||||||
will be called where 'file' has 'pkgname-config-blob' as its content.
|
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
|
If 'packages_dir' has files in it that end in '.snap', then they are
|
||||||
installed. Given 3 files:
|
installed. Given 3 files:
|
||||||
<packages_dir>/foo.snap
|
<packages_dir>/foo.snap
|
||||||
@ -52,6 +56,7 @@ LOG = logging.getLogger(__name__)
|
|||||||
|
|
||||||
frequency = PER_INSTANCE
|
frequency = PER_INSTANCE
|
||||||
SNAPPY_CMD = "snappy"
|
SNAPPY_CMD = "snappy"
|
||||||
|
NAMESPACE_DELIM = '.'
|
||||||
|
|
||||||
BUILTIN_CFG = {
|
BUILTIN_CFG = {
|
||||||
'packages': [],
|
'packages': [],
|
||||||
@ -81,10 +86,20 @@ def makeop(op, name, config=None, path=None, cfgfile=None):
|
|||||||
'cfgfile': cfgfile})
|
'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):
|
def get_package_ops(packages, configs, installed=None, fspath=None):
|
||||||
# get the install an config operations that should be done
|
# get the install an config operations that should be done
|
||||||
if installed is None:
|
if installed is None:
|
||||||
installed = read_installed_packages()
|
installed = read_installed_packages()
|
||||||
|
short_installed = [p.partition(NAMESPACE_DELIM)[0] for p in installed]
|
||||||
|
|
||||||
if not packages:
|
if not packages:
|
||||||
packages = []
|
packages = []
|
||||||
@ -95,21 +110,31 @@ def get_package_ops(packages, configs, installed=None, fspath=None):
|
|||||||
ops += get_fs_package_ops(fspath)
|
ops += get_fs_package_ops(fspath)
|
||||||
|
|
||||||
for name in packages:
|
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]
|
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:
|
for name in configs:
|
||||||
if name in installed and name not in to_install:
|
if name in to_install:
|
||||||
ops.append(makeop('config', name, config=configs[name]))
|
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
|
# prefer config entries to filepath entries
|
||||||
for op in ops:
|
for op in ops:
|
||||||
|
if op['op'] != 'install' or not op['cfgfile']:
|
||||||
|
continue
|
||||||
name = op['name']
|
name = op['name']
|
||||||
if name in configs and op['op'] == 'install' and 'cfgfile' in op:
|
fromcfg = get_package_config(configs, op['name'])
|
||||||
LOG.debug("preferring configs[%s] over '%s'", name, op['cfgfile'])
|
if fromcfg:
|
||||||
|
LOG.debug("preferring configs[%(name)s] over '%(cfgfile)s'", op)
|
||||||
op['cfgfile'] = None
|
op['cfgfile'] = None
|
||||||
op['config'] = configs[op['name']]
|
op['config'] = fromcfg
|
||||||
|
|
||||||
return ops
|
return ops
|
||||||
|
|
||||||
@ -118,6 +143,7 @@ def render_snap_op(op, name, path=None, cfgfile=None, config=None):
|
|||||||
if op not in ('install', 'config'):
|
if op not in ('install', 'config'):
|
||||||
raise ValueError("cannot render op '%s'" % op)
|
raise ValueError("cannot render op '%s'" % op)
|
||||||
|
|
||||||
|
shortname = name.partition(NAMESPACE_DELIM)[0]
|
||||||
try:
|
try:
|
||||||
cfg_tmpf = None
|
cfg_tmpf = None
|
||||||
if config is not None:
|
if config is not None:
|
||||||
@ -126,7 +152,7 @@ def render_snap_op(op, name, path=None, cfgfile=None, config=None):
|
|||||||
# packagename:
|
# packagename:
|
||||||
# config
|
# config
|
||||||
# Note, however, we do not touch config files on disk.
|
# 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()
|
(fd, cfg_tmpf) = tempfile.mkstemp()
|
||||||
os.write(fd, util.yaml_dumps(nested_cfg).encode())
|
os.write(fd, util.yaml_dumps(nested_cfg).encode())
|
||||||
os.close(fd)
|
os.close(fd)
|
||||||
@ -151,7 +177,13 @@ def render_snap_op(op, name, path=None, cfgfile=None, config=None):
|
|||||||
|
|
||||||
|
|
||||||
def read_installed_packages():
|
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():
|
def read_pkg_data():
|
||||||
|
@ -90,6 +90,15 @@ class TestInstallPackages(t_help.TestCase):
|
|||||||
makeop('install', 'pkg2', b'mycfg2'),
|
makeop('install', 'pkg2', b'mycfg2'),
|
||||||
makeop('config', 'xinstalled', b'xcfg')])
|
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):
|
def test_package_ops_with_file(self):
|
||||||
self.populate_tmp(
|
self.populate_tmp(
|
||||||
{"snapf1.snap": b"foo1", "snapf1.config": b"snapf1cfg",
|
{"snapf1.snap": b"foo1", "snapf1.config": b"snapf1cfg",
|
||||||
@ -114,6 +123,34 @@ class TestInstallPackages(t_help.TestCase):
|
|||||||
ret, [makeop_tmpd(self.tmp, 'install', 'snapf1',
|
ret, [makeop_tmpd(self.tmp, 'install', 'snapf1',
|
||||||
path="snapf1.snap", config="snapf1cfg-config")])
|
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):
|
def test_render_op_localsnap(self):
|
||||||
self.populate_tmp({"snapf1.snap": b"foo1"})
|
self.populate_tmp({"snapf1.snap": b"foo1"})
|
||||||
op = makeop_tmpd(self.tmp, 'install', 'snapf1',
|
op = makeop_tmpd(self.tmp, 'install', 'snapf1',
|
||||||
@ -190,6 +227,15 @@ class TestInstallPackages(t_help.TestCase):
|
|||||||
data_found = self.snapcmds[0][2]
|
data_found = self.snapcmds[0][2]
|
||||||
self.assertEqual(mycfg, data_found['config'][name])
|
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):
|
def test_render_does_not_pad_cfgfile(self):
|
||||||
# package_ops with cfgfile should not modify --file= content.
|
# package_ops with cfgfile should not modify --file= content.
|
||||||
mydata = "foo1: bar1\nk: [l1, l2, l3]\n"
|
mydata = "foo1: bar1\nk: [l1, l2, l3]\n"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user