Merge ~chad.smith/cloud-init:bug/drop-deprecated-snap-modules into cloud-init:master

Proposed by Chad Smith
Status: Rejected
Rejected by: Chad Smith
Proposed branch: ~chad.smith/cloud-init:bug/drop-deprecated-snap-modules
Merge into: cloud-init:master
Diff against target: 1201 lines (+0/-609)
3 files modified
dev/null (+0/-601)
doc/rtd/topics/modules.rst (+0/-2)
tests/cloud_tests/testcases/modules/TODO.md (+0/-6)
Reviewer Review Type Date Requested Status
Ryan Harper Needs Information
Server Team CI bot continuous-integration Needs Fixing
Review via email: mp+362691@code.launchpad.net

Commit message

snap_config/snappy: Drop deprecated cloud-config modules

Both snap_config and snappy modules have been deprecated in 18.3.
We should have dropped deprecated modules in that timeframe as the
functionality is replaced by the snap module and a commands element.

Cloud-config that used to provided snappy assertions and configuration
can now instead use the top-level snap: and assertions or commands.

Detailed docs here:
   https://cloudinit.readthedocs.io/en/latest/topics/modules.html#snap

LP: #1814296

To post a comment you must log in.
Revision history for this message
Server Team CI bot (server-team-bot) wrote :

FAILED: Continuous integration, rev:a2d9c10f02a47a7403f1102b7f4694ea79961c11
https://jenkins.ubuntu.com/server/job/cloud-init-ci/555/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    FAILED: Ubuntu LTS: Integration

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/555/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Ryan Harper (raharper) wrote :

Hrm, what should we do on upgrade?

review: Needs Information
Revision history for this message
Chad Smith (chad.smith) wrote :

Unmerged commits

a2d9c10... by Chad Smith

snap_config/snappy: Drop deprecated cloud-config modules

Both snap_config and snappy modules have been deprecated in 18.3.
We should have dropped deprecated modules in that timeframe as the
functionality is replaced by the snap module and a commands element.

Cloud-config that used to provided snappy assertions and configuration
can now instead use the top-level snap: and assertions or commands.

Detailed docs here:
   https://cloudinit.readthedocs.io/en/latest/topics/modules.html#snap

LP: #1814296

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/cloudinit/config/cc_snap_config.py b/cloudinit/config/cc_snap_config.py
2deleted file mode 100644
3index afe297e..0000000
4--- a/cloudinit/config/cc_snap_config.py
5+++ /dev/null
6@@ -1,184 +0,0 @@
7-# Copyright (C) 2016 Canonical Ltd.
8-#
9-# Author: Ryan Harper <ryan.harper@canonical.com>
10-#
11-# This file is part of cloud-init. See LICENSE file for license information.
12-
13-# RELEASE_BLOCKER: Remove this deprecated module in 18.3
14-"""
15-Snap Config
16------------
17-**Summary:** snap_config modules allows configuration of snapd.
18-
19-**Deprecated**: Use :ref:`snap` module instead. This module will not exist
20-in cloud-init 18.3.
21-
22-This module uses the same ``snappy`` namespace for configuration but
23-acts only only a subset of the configuration.
24-
25-If ``assertions`` is set and the user has included a list of assertions
26-then cloud-init will collect the assertions into a single assertion file
27-and invoke ``snap ack <path to file with assertions>`` which will attempt
28-to load the provided assertions into the snapd assertion database.
29-
30-If ``email`` is set, this value is used to create an authorized user for
31-contacting and installing snaps from the Ubuntu Store. This is done by
32-calling ``snap create-user`` command.
33-
34-If ``known`` is set to True, then it is expected the user also included
35-an assertion of type ``system-user``. When ``snap create-user`` is called
36-cloud-init will append '--known' flag which instructs snapd to look for
37-a system-user assertion with the details. If ``known`` is not set, then
38-``snap create-user`` will contact the Ubuntu SSO for validating and importing
39-a system-user for the instance.
40-
41-.. note::
42- If the system is already managed, then cloud-init will not attempt to
43- create a system-user.
44-
45-**Internal name:** ``cc_snap_config``
46-
47-**Module frequency:** per instance
48-
49-**Supported distros:** any with 'snapd' available
50-
51-**Config keys**::
52-
53- #cloud-config
54- snappy:
55- assertions:
56- - |
57- <assertion 1>
58- - |
59- <assertion 2>
60- email: user@user.org
61- known: true
62-
63-"""
64-
65-from cloudinit import log as logging
66-from cloudinit.settings import PER_INSTANCE
67-from cloudinit import util
68-
69-LOG = logging.getLogger(__name__)
70-
71-frequency = PER_INSTANCE
72-SNAPPY_CMD = "snap"
73-ASSERTIONS_FILE = "/var/lib/cloud/instance/snapd.assertions"
74-
75-
76-"""
77-snappy:
78- assertions:
79- - |
80- <snap assertion 1>
81- - |
82- <snap assertion 2>
83- email: foo@foo.io
84- known: true
85-"""
86-
87-
88-def add_assertions(assertions=None):
89- """Import list of assertions.
90-
91- Import assertions by concatenating each assertion into a
92- string separated by a '\n'. Write this string to a instance file and
93- then invoke `snap ack /path/to/file` and check for errors.
94- If snap exits 0, then all assertions are imported.
95- """
96- if not assertions:
97- assertions = []
98-
99- if not isinstance(assertions, list):
100- raise ValueError(
101- 'assertion parameter was not a list: {assertions}'.format(
102- assertions=assertions))
103-
104- snap_cmd = [SNAPPY_CMD, 'ack']
105- combined = "\n".join(assertions)
106- if len(combined) == 0:
107- raise ValueError("Assertion list is empty")
108-
109- for asrt in assertions:
110- LOG.debug('Acking: %s', asrt.split('\n')[0:2])
111-
112- util.write_file(ASSERTIONS_FILE, combined.encode('utf-8'))
113- util.subp(snap_cmd + [ASSERTIONS_FILE], capture=True)
114-
115-
116-def add_snap_user(cfg=None):
117- """Add a snap system-user if provided with email under snappy config.
118-
119- - Check that system is not already managed.
120- - Check that if using a system-user assertion, that it's
121- imported into snapd.
122-
123- Returns a dictionary to be passed to Distro.create_user
124- """
125-
126- if not cfg:
127- cfg = {}
128-
129- if not isinstance(cfg, dict):
130- raise ValueError(
131- 'configuration parameter was not a dict: {cfg}'.format(cfg=cfg))
132-
133- snapuser = cfg.get('email', None)
134- if not snapuser:
135- return
136-
137- usercfg = {
138- 'snapuser': snapuser,
139- 'known': cfg.get('known', False),
140- }
141-
142- # query if we're already registered
143- out, _ = util.subp([SNAPPY_CMD, 'managed'], capture=True)
144- if out.strip() == "true":
145- LOG.warning('This device is already managed. '
146- 'Skipping system-user creation')
147- return
148-
149- if usercfg.get('known'):
150- # Check that we imported a system-user assertion
151- out, _ = util.subp([SNAPPY_CMD, 'known', 'system-user'],
152- capture=True)
153- if len(out) == 0:
154- LOG.error('Missing "system-user" assertion. '
155- 'Check "snappy" user-data assertions.')
156- return
157-
158- return usercfg
159-
160-
161-def handle(name, cfg, cloud, log, args):
162- cfgin = cfg.get('snappy')
163- if not cfgin:
164- LOG.debug('No snappy config provided, skipping')
165- return
166-
167- log.warning(
168- 'DEPRECATION: snap_config module will be dropped in 18.3 release.'
169- ' Use snap module instead')
170- if not(util.system_is_snappy()):
171- LOG.debug("%s: system not snappy", name)
172- return
173-
174- assertions = cfgin.get('assertions', [])
175- if len(assertions) > 0:
176- LOG.debug('Importing user-provided snap assertions')
177- add_assertions(assertions)
178-
179- # Create a snap user if requested.
180- # Snap systems contact the store with a user's email
181- # and extract information needed to create a local user.
182- # A user may provide a 'system-user' assertion which includes
183- # the required information. Using such an assertion to create
184- # a local user requires specifying 'known: true' in the supplied
185- # user-data.
186- usercfg = add_snap_user(cfg=cfgin)
187- if usercfg:
188- cloud.distro.create_user(usercfg.get('snapuser'), **usercfg)
189-
190-# vi: ts=4 expandtab
191diff --git a/cloudinit/config/cc_snappy.py b/cloudinit/config/cc_snappy.py
192deleted file mode 100644
193index 15bee2d..0000000
194--- a/cloudinit/config/cc_snappy.py
195+++ /dev/null
196@@ -1,321 +0,0 @@
197-# This file is part of cloud-init. See LICENSE file for license information.
198-
199-# RELEASE_BLOCKER: Remove this deprecated module in 18.3
200-"""
201-Snappy
202-------
203-**Summary:** snappy modules allows configuration of snappy.
204-
205-**Deprecated**: Use :ref:`snap` module instead. This module will not exist
206-in cloud-init 18.3.
207-
208-The below example config config would install ``etcd``, and then install
209-``pkg2.smoser`` with a ``<config-file>`` argument where ``config-file`` has
210-``config-blob`` inside it. If ``pkgname`` is installed already, then
211-``snappy config pkgname <file>``
212-will be called where ``file`` has ``pkgname-config-blob`` as its content.
213-
214-Entries in ``config`` can be namespaced or non-namespaced for a package.
215-In either case, the config provided to snappy command is non-namespaced.
216-The package name is provided as it appears.
217-
218-If ``packages_dir`` has files in it that end in ``.snap``, then they are
219-installed. Given 3 files:
220-
221- - <packages_dir>/foo.snap
222- - <packages_dir>/foo.config
223- - <packages_dir>/bar.snap
224-
225-cloud-init will invoke:
226-
227- - snappy install <packages_dir>/foo.snap <packages_dir>/foo.config
228- - snappy install <packages_dir>/bar.snap
229-
230-.. note::
231- that if provided a ``config`` entry for ``ubuntu-core``, then
232- cloud-init will invoke: snappy config ubuntu-core <config>
233- Allowing you to configure ubuntu-core in this way.
234-
235-The ``ssh_enabled`` key controls the system's ssh service. The default value
236-is ``auto``. Options are:
237-
238- - **True:** enable ssh service
239- - **False:** disable ssh service
240- - **auto:** enable ssh service if either ssh keys have been provided
241- or user has requested password authentication (ssh_pwauth).
242-
243-**Internal name:** ``cc_snappy``
244-
245-**Module frequency:** per instance
246-
247-**Supported distros:** ubuntu
248-
249-**Config keys**::
250-
251- #cloud-config
252- snappy:
253- system_snappy: auto
254- ssh_enabled: auto
255- packages: [etcd, pkg2.smoser]
256- config:
257- pkgname:
258- key2: value2
259- pkg2:
260- key1: value1
261- packages_dir: '/writable/user-data/cloud-init/snaps'
262-"""
263-
264-from cloudinit import log as logging
265-from cloudinit.settings import PER_INSTANCE
266-from cloudinit import temp_utils
267-from cloudinit import util
268-
269-import glob
270-import os
271-
272-LOG = logging.getLogger(__name__)
273-
274-frequency = PER_INSTANCE
275-SNAPPY_CMD = "snappy"
276-NAMESPACE_DELIM = '.'
277-
278-BUILTIN_CFG = {
279- 'packages': [],
280- 'packages_dir': '/writable/user-data/cloud-init/snaps',
281- 'ssh_enabled': "auto",
282- 'system_snappy': "auto",
283- 'config': {},
284-}
285-
286-distros = ['ubuntu']
287-
288-
289-def parse_filename(fname):
290- fname = os.path.basename(fname)
291- fname_noext = fname.rpartition(".")[0]
292- name = fname_noext.partition("_")[0]
293- shortname = name.partition(".")[0]
294- return(name, shortname, fname_noext)
295-
296-
297-def get_fs_package_ops(fspath):
298- if not fspath:
299- return []
300- ops = []
301- for snapfile in sorted(glob.glob(os.path.sep.join([fspath, '*.snap']))):
302- (name, shortname, fname_noext) = parse_filename(snapfile)
303- cfg = None
304- for cand in (fname_noext, name, shortname):
305- fpcand = os.path.sep.join([fspath, cand]) + ".config"
306- if os.path.isfile(fpcand):
307- cfg = fpcand
308- break
309- ops.append(makeop('install', name, config=None,
310- path=snapfile, cfgfile=cfg))
311- return ops
312-
313-
314-def makeop(op, name, config=None, path=None, cfgfile=None):
315- return({'op': op, 'name': name, 'config': config, 'path': path,
316- 'cfgfile': cfgfile})
317-
318-
319-def get_package_config(configs, name):
320- # load the package's config from the configs dict.
321- # prefer full-name entry (config-example.canonical)
322- # over short name entry (config-example)
323- if name in configs:
324- return configs[name]
325- return configs.get(name.partition(NAMESPACE_DELIM)[0])
326-
327-
328-def get_package_ops(packages, configs, installed=None, fspath=None):
329- # get the install an config operations that should be done
330- if installed is None:
331- installed = read_installed_packages()
332- short_installed = [p.partition(NAMESPACE_DELIM)[0] for p in installed]
333-
334- if not packages:
335- packages = []
336- if not configs:
337- configs = {}
338-
339- ops = []
340- ops += get_fs_package_ops(fspath)
341-
342- for name in packages:
343- ops.append(makeop('install', name, get_package_config(configs, name)))
344-
345- to_install = [f['name'] for f in ops]
346- short_to_install = [f['name'].partition(NAMESPACE_DELIM)[0] for f in ops]
347-
348- for name in configs:
349- if name in to_install:
350- continue
351- shortname = name.partition(NAMESPACE_DELIM)[0]
352- if shortname in short_to_install:
353- continue
354- if name in installed or shortname in short_installed:
355- ops.append(makeop('config', name,
356- config=get_package_config(configs, name)))
357-
358- # prefer config entries to filepath entries
359- for op in ops:
360- if op['op'] != 'install' or not op['cfgfile']:
361- continue
362- name = op['name']
363- fromcfg = get_package_config(configs, op['name'])
364- if fromcfg:
365- LOG.debug("preferring configs[%(name)s] over '%(cfgfile)s'", op)
366- op['cfgfile'] = None
367- op['config'] = fromcfg
368-
369- return ops
370-
371-
372-def render_snap_op(op, name, path=None, cfgfile=None, config=None):
373- if op not in ('install', 'config'):
374- raise ValueError("cannot render op '%s'" % op)
375-
376- shortname = name.partition(NAMESPACE_DELIM)[0]
377- try:
378- cfg_tmpf = None
379- if config is not None:
380- # input to 'snappy config packagename' must have nested data. odd.
381- # config:
382- # packagename:
383- # config
384- # Note, however, we do not touch config files on disk.
385- nested_cfg = {'config': {shortname: config}}
386- (fd, cfg_tmpf) = temp_utils.mkstemp()
387- os.write(fd, util.yaml_dumps(nested_cfg).encode())
388- os.close(fd)
389- cfgfile = cfg_tmpf
390-
391- cmd = [SNAPPY_CMD, op]
392- if op == 'install':
393- if path:
394- cmd.append("--allow-unauthenticated")
395- cmd.append(path)
396- else:
397- cmd.append(name)
398- if cfgfile:
399- cmd.append(cfgfile)
400- elif op == 'config':
401- cmd += [name, cfgfile]
402-
403- util.subp(cmd)
404-
405- finally:
406- if cfg_tmpf:
407- os.unlink(cfg_tmpf)
408-
409-
410-def read_installed_packages():
411- ret = []
412- for (name, _date, _version, dev) in read_pkg_data():
413- if dev:
414- ret.append(NAMESPACE_DELIM.join([name, dev]))
415- else:
416- ret.append(name)
417- return ret
418-
419-
420-def read_pkg_data():
421- out, _err = util.subp([SNAPPY_CMD, "list"])
422- pkg_data = []
423- for line in out.splitlines()[1:]:
424- toks = line.split(sep=None, maxsplit=3)
425- if len(toks) == 3:
426- (name, date, version) = toks
427- dev = None
428- else:
429- (name, date, version, dev) = toks
430- pkg_data.append((name, date, version, dev,))
431- return pkg_data
432-
433-
434-def disable_enable_ssh(enabled):
435- LOG.debug("setting enablement of ssh to: %s", enabled)
436- # do something here that would enable or disable
437- not_to_be_run = "/etc/ssh/sshd_not_to_be_run"
438- if enabled:
439- util.del_file(not_to_be_run)
440- # this is an indempotent operation
441- util.subp(["systemctl", "start", "ssh"])
442- else:
443- # this is an indempotent operation
444- util.subp(["systemctl", "stop", "ssh"])
445- util.write_file(not_to_be_run, "cloud-init\n")
446-
447-
448-def set_snappy_command():
449- global SNAPPY_CMD
450- if util.which("snappy-go"):
451- SNAPPY_CMD = "snappy-go"
452- elif util.which("snappy"):
453- SNAPPY_CMD = "snappy"
454- else:
455- SNAPPY_CMD = "snap"
456- LOG.debug("snappy command is '%s'", SNAPPY_CMD)
457-
458-
459-def handle(name, cfg, cloud, log, args):
460- cfgin = cfg.get('snappy')
461- if not cfgin:
462- cfgin = {}
463- mycfg = util.mergemanydict([cfgin, BUILTIN_CFG])
464-
465- sys_snappy = str(mycfg.get("system_snappy", "auto"))
466- if util.is_false(sys_snappy):
467- LOG.debug("%s: System is not snappy. disabling", name)
468- return
469-
470- if sys_snappy.lower() == "auto" and not(util.system_is_snappy()):
471- LOG.debug("%s: 'auto' mode, and system not snappy", name)
472- return
473-
474- log.warning(
475- 'DEPRECATION: snappy module will be dropped in 18.3 release.'
476- ' Use snap module instead')
477-
478- set_snappy_command()
479-
480- pkg_ops = get_package_ops(packages=mycfg['packages'],
481- configs=mycfg['config'],
482- fspath=mycfg['packages_dir'])
483-
484- fails = []
485- for pkg_op in pkg_ops:
486- try:
487- render_snap_op(**pkg_op)
488- except Exception as e:
489- fails.append((pkg_op, e,))
490- LOG.warning("'%s' failed for '%s': %s",
491- pkg_op['op'], pkg_op['name'], e)
492-
493- # Default to disabling SSH
494- ssh_enabled = mycfg.get('ssh_enabled', "auto")
495-
496- # If the user has not explicitly enabled or disabled SSH, then enable it
497- # when password SSH authentication is requested or there are SSH keys
498- if ssh_enabled == "auto":
499- user_ssh_keys = cloud.get_public_ssh_keys() or None
500- password_auth_enabled = cfg.get('ssh_pwauth', False)
501- if user_ssh_keys:
502- LOG.debug("Enabling SSH, ssh keys found in datasource")
503- ssh_enabled = True
504- elif cfg.get('ssh_authorized_keys'):
505- LOG.debug("Enabling SSH, ssh keys found in config")
506- elif password_auth_enabled:
507- LOG.debug("Enabling SSH, password authentication requested")
508- ssh_enabled = True
509- elif ssh_enabled not in (True, False):
510- LOG.warning("Unknown value '%s' in ssh_enabled", ssh_enabled)
511-
512- disable_enable_ssh(ssh_enabled)
513-
514- if fails:
515- raise Exception("failed to install/configure snaps")
516-
517-# vi: ts=4 expandtab
518diff --git a/doc/rtd/topics/modules.rst b/doc/rtd/topics/modules.rst
519index d9720f6..d4dce24 100644
520--- a/doc/rtd/topics/modules.rst
521+++ b/doc/rtd/topics/modules.rst
522@@ -46,8 +46,6 @@ Modules
523 .. automodule:: cloudinit.config.cc_set_hostname
524 .. automodule:: cloudinit.config.cc_set_passwords
525 .. automodule:: cloudinit.config.cc_snap
526-.. automodule:: cloudinit.config.cc_snappy
527-.. automodule:: cloudinit.config.cc_snap_config
528 .. automodule:: cloudinit.config.cc_spacewalk
529 .. automodule:: cloudinit.config.cc_ssh
530 .. automodule:: cloudinit.config.cc_ssh_authkey_fingerprints
531diff --git a/tests/cloud_tests/testcases/modules/TODO.md b/tests/cloud_tests/testcases/modules/TODO.md
532index 0b933b3..2a63609 100644
533--- a/tests/cloud_tests/testcases/modules/TODO.md
534+++ b/tests/cloud_tests/testcases/modules/TODO.md
535@@ -78,12 +78,6 @@ Not applicable to write a test for this as it specifies when something should be
536 ## scripts vendor
537 Not applicable to write a test for this as it specifies when something should be run.
538
539-## snappy
540-2016-11-17: Need test to install snaps from store
541-
542-## snap-config
543-2016-11-17: Need to investigate
544-
545 ## spacewalk
546
547 ## ssh authkey fingerprints
548diff --git a/tests/cloud_tests/testcases/modules/snappy.py b/tests/cloud_tests/testcases/modules/snappy.py
549deleted file mode 100644
550index 7d17fc5..0000000
551--- a/tests/cloud_tests/testcases/modules/snappy.py
552+++ /dev/null
553@@ -1,17 +0,0 @@
554-# This file is part of cloud-init. See LICENSE file for license information.
555-
556-"""cloud-init Integration Test Verify Script"""
557-from tests.cloud_tests.testcases import base
558-
559-
560-class TestSnappy(base.CloudTestCase):
561- """Test snappy module"""
562-
563- expected_warnings = ('DEPRECATION',)
564-
565- def test_snappy_version(self):
566- """Test snappy version output"""
567- out = self.get_data_file('snapd')
568- self.assertIn('Status: install ok installed', out)
569-
570-# vi: ts=4 expandtab
571diff --git a/tests/cloud_tests/testcases/modules/snappy.yaml b/tests/cloud_tests/testcases/modules/snappy.yaml
572deleted file mode 100644
573index 8ac322a..0000000
574--- a/tests/cloud_tests/testcases/modules/snappy.yaml
575+++ /dev/null
576@@ -1,18 +0,0 @@
577-#
578-# Install snappy
579-#
580-# Aug 17, 2018: Disabled due to requiring a proxy for testing
581-# tests do not handle the proxy well at this time.
582-enabled: False
583-required_features:
584- - snap
585-cloud_config: |
586- #cloud-config
587- snappy:
588- system_snappy: auto
589-collect_scripts:
590- snapd: |
591- #!/bin/bash
592- dpkg -s snapd
593-
594-# vi: ts=4 expandtab
595diff --git a/tests/unittests/test_handler/test_handler_snappy.py b/tests/unittests/test_handler/test_handler_snappy.py
596deleted file mode 100644
597index 76b79c2..0000000
598--- a/tests/unittests/test_handler/test_handler_snappy.py
599+++ /dev/null
600@@ -1,601 +0,0 @@
601-# This file is part of cloud-init. See LICENSE file for license information.
602-
603-from cloudinit.config.cc_snappy import (
604- makeop, get_package_ops, render_snap_op)
605-from cloudinit.config.cc_snap_config import (
606- add_assertions, add_snap_user, ASSERTIONS_FILE)
607-from cloudinit import (distros, helpers, cloud, util)
608-from cloudinit.config.cc_snap_config import handle as snap_handle
609-from cloudinit.sources import DataSourceNone
610-from cloudinit.tests.helpers import FilesystemMockingTestCase, mock
611-
612-from cloudinit.tests import helpers as t_help
613-
614-import logging
615-import os
616-import shutil
617-import tempfile
618-import textwrap
619-import yaml
620-
621-LOG = logging.getLogger(__name__)
622-ALLOWED = (dict, list, int, str)
623-
624-
625-class TestInstallPackages(t_help.TestCase):
626- def setUp(self):
627- super(TestInstallPackages, self).setUp()
628- self.unapply = []
629-
630- # by default 'which' has nothing in its path
631- self.apply_patches([(util, 'subp', self._subp)])
632- self.subp_called = []
633- self.snapcmds = []
634- self.tmp = tempfile.mkdtemp(prefix="TestInstallPackages")
635-
636- def tearDown(self):
637- apply_patches([i for i in reversed(self.unapply)])
638- shutil.rmtree(self.tmp)
639-
640- def apply_patches(self, patches):
641- ret = apply_patches(patches)
642- self.unapply += ret
643-
644- def populate_tmp(self, files):
645- return t_help.populate_dir(self.tmp, files)
646-
647- def _subp(self, *args, **kwargs):
648- # supports subp calling with cmd as args or kwargs
649- if 'args' not in kwargs:
650- kwargs['args'] = args[0]
651- self.subp_called.append(kwargs)
652- args = kwargs['args']
653- # here we basically parse the snappy command invoked
654- # and append to snapcmds a list of (mode, pkg, config)
655- if args[0:2] == ['snappy', 'config']:
656- if args[3] == "-":
657- config = kwargs.get('data', '')
658- else:
659- with open(args[3], "rb") as fp:
660- config = yaml.safe_load(fp.read())
661- self.snapcmds.append(['config', args[2], config])
662- elif args[0:2] == ['snappy', 'install']:
663- config = None
664- pkg = None
665- for arg in args[2:]:
666- if arg.startswith("-"):
667- continue
668- if not pkg:
669- pkg = arg
670- elif not config:
671- cfgfile = arg
672- if cfgfile == "-":
673- config = kwargs.get('data', '')
674- elif cfgfile:
675- with open(cfgfile, "rb") as fp:
676- config = yaml.safe_load(fp.read())
677- self.snapcmds.append(['install', pkg, config])
678-
679- def test_package_ops_1(self):
680- ret = get_package_ops(
681- packages=['pkg1', 'pkg2', 'pkg3'],
682- configs={'pkg2': b'mycfg2'}, installed=[])
683- self.assertEqual(
684- ret, [makeop('install', 'pkg1', None, None),
685- makeop('install', 'pkg2', b'mycfg2', None),
686- makeop('install', 'pkg3', None, None)])
687-
688- def test_package_ops_config_only(self):
689- ret = get_package_ops(
690- packages=None,
691- configs={'pkg2': b'mycfg2'}, installed=['pkg1', 'pkg2'])
692- self.assertEqual(
693- ret, [makeop('config', 'pkg2', b'mycfg2')])
694-
695- def test_package_ops_install_and_config(self):
696- ret = get_package_ops(
697- packages=['pkg3', 'pkg2'],
698- configs={'pkg2': b'mycfg2', 'xinstalled': b'xcfg'},
699- installed=['xinstalled'])
700- self.assertEqual(
701- ret, [makeop('install', 'pkg3'),
702- makeop('install', 'pkg2', b'mycfg2'),
703- makeop('config', 'xinstalled', b'xcfg')])
704-
705- def test_package_ops_install_long_config_short(self):
706- # a package can be installed by full name, but have config by short
707- cfg = {'k1': 'k2'}
708- ret = get_package_ops(
709- packages=['config-example.canonical'],
710- configs={'config-example': cfg}, installed=[])
711- self.assertEqual(
712- ret, [makeop('install', 'config-example.canonical', cfg)])
713-
714- def test_package_ops_with_file(self):
715- self.populate_tmp(
716- {"snapf1.snap": b"foo1", "snapf1.config": b"snapf1cfg",
717- "snapf2.snap": b"foo2", "foo.bar": "ignored"})
718- ret = get_package_ops(
719- packages=['pkg1'], configs={}, installed=[], fspath=self.tmp)
720- self.assertEqual(
721- ret,
722- [makeop_tmpd(self.tmp, 'install', 'snapf1', path="snapf1.snap",
723- cfgfile="snapf1.config"),
724- makeop_tmpd(self.tmp, 'install', 'snapf2', path="snapf2.snap"),
725- makeop('install', 'pkg1')])
726-
727- def test_package_ops_common_filename(self):
728- # fish package name from filename
729- # package names likely look like: pkgname.namespace_version_arch.snap
730-
731- # find filenames
732- self.populate_tmp(
733- {"pkg-ws.smoser_0.3.4_all.snap": "pkg-ws-snapdata",
734- "pkg-ws.config": "pkg-ws-config",
735- "pkg1.smoser_1.2.3_all.snap": "pkg1.snapdata",
736- "pkg1.smoser.config": "pkg1.smoser.config-data",
737- "pkg1.config": "pkg1.config-data",
738- "pkg2.smoser_0.0_amd64.snap": "pkg2-snapdata",
739- "pkg2.smoser_0.0_amd64.config": "pkg2.config"})
740-
741- ret = get_package_ops(
742- packages=[], configs={}, installed=[], fspath=self.tmp)
743- self.assertEqual(
744- ret,
745- [makeop_tmpd(self.tmp, 'install', 'pkg-ws.smoser',
746- path="pkg-ws.smoser_0.3.4_all.snap",
747- cfgfile="pkg-ws.config"),
748- makeop_tmpd(self.tmp, 'install', 'pkg1.smoser',
749- path="pkg1.smoser_1.2.3_all.snap",
750- cfgfile="pkg1.smoser.config"),
751- makeop_tmpd(self.tmp, 'install', 'pkg2.smoser',
752- path="pkg2.smoser_0.0_amd64.snap",
753- cfgfile="pkg2.smoser_0.0_amd64.config"),
754- ])
755-
756- def test_package_ops_config_overrides_file(self):
757- # config data overrides local file .config
758- self.populate_tmp(
759- {"snapf1.snap": b"foo1", "snapf1.config": b"snapf1cfg"})
760- ret = get_package_ops(
761- packages=[], configs={'snapf1': 'snapf1cfg-config'},
762- installed=[], fspath=self.tmp)
763- self.assertEqual(
764- ret, [makeop_tmpd(self.tmp, 'install', 'snapf1',
765- path="snapf1.snap", config="snapf1cfg-config")])
766-
767- def test_package_ops_namespacing(self):
768- cfgs = {
769- 'config-example': {'k1': 'v1'},
770- 'pkg1': {'p1': 'p2'},
771- 'ubuntu-core': {'c1': 'c2'},
772- 'notinstalled.smoser': {'s1': 's2'},
773- }
774- ret = get_package_ops(
775- packages=['config-example.canonical'], configs=cfgs,
776- installed=['config-example.smoser', 'pkg1.canonical',
777- 'ubuntu-core'])
778-
779- expected_configs = [
780- makeop('config', 'pkg1', config=cfgs['pkg1']),
781- makeop('config', 'ubuntu-core', config=cfgs['ubuntu-core'])]
782- expected_installs = [
783- makeop('install', 'config-example.canonical',
784- config=cfgs['config-example'])]
785-
786- installs = [i for i in ret if i['op'] == 'install']
787- configs = [c for c in ret if c['op'] == 'config']
788-
789- self.assertEqual(installs, expected_installs)
790- # configs are not ordered
791- self.assertEqual(len(configs), len(expected_configs))
792- self.assertTrue(all(found in expected_configs for found in configs))
793-
794- def test_render_op_localsnap(self):
795- self.populate_tmp({"snapf1.snap": b"foo1"})
796- op = makeop_tmpd(self.tmp, 'install', 'snapf1',
797- path='snapf1.snap')
798- render_snap_op(**op)
799- self.assertEqual(
800- self.snapcmds, [['install', op['path'], None]])
801-
802- def test_render_op_localsnap_localconfig(self):
803- self.populate_tmp(
804- {"snapf1.snap": b"foo1", 'snapf1.config': b'snapf1cfg'})
805- op = makeop_tmpd(self.tmp, 'install', 'snapf1',
806- path='snapf1.snap', cfgfile='snapf1.config')
807- render_snap_op(**op)
808- self.assertEqual(
809- self.snapcmds, [['install', op['path'], 'snapf1cfg']])
810-
811- def test_render_op_snap(self):
812- op = makeop('install', 'snapf1')
813- render_snap_op(**op)
814- self.assertEqual(
815- self.snapcmds, [['install', 'snapf1', None]])
816-
817- def test_render_op_snap_config(self):
818- mycfg = {'key1': 'value1'}
819- name = "snapf1"
820- op = makeop('install', name, config=mycfg)
821- render_snap_op(**op)
822- self.assertEqual(
823- self.snapcmds, [['install', name, {'config': {name: mycfg}}]])
824-
825- def test_render_op_config_bytes(self):
826- name = "snapf1"
827- mycfg = b'myconfig'
828- op = makeop('config', name, config=mycfg)
829- render_snap_op(**op)
830- self.assertEqual(
831- self.snapcmds, [['config', 'snapf1', {'config': {name: mycfg}}]])
832-
833- def test_render_op_config_string(self):
834- name = 'snapf1'
835- mycfg = 'myconfig: foo\nhisconfig: bar\n'
836- op = makeop('config', name, config=mycfg)
837- render_snap_op(**op)
838- self.assertEqual(
839- self.snapcmds, [['config', 'snapf1', {'config': {name: mycfg}}]])
840-
841- def test_render_op_config_dict(self):
842- # config entry for package can be a dict, not a string blob
843- mycfg = {'foo': 'bar'}
844- name = 'snapf1'
845- op = makeop('config', name, config=mycfg)
846- render_snap_op(**op)
847- # snapcmds is a list of 3-entry lists. data_found will be the
848- # blob of data in the file in 'snappy install --config=<file>'
849- data_found = self.snapcmds[0][2]
850- self.assertEqual(mycfg, data_found['config'][name])
851-
852- def test_render_op_config_list(self):
853- # config entry for package can be a list, not a string blob
854- mycfg = ['foo', 'bar', 'wark', {'f1': 'b1'}]
855- name = "snapf1"
856- op = makeop('config', name, config=mycfg)
857- render_snap_op(**op)
858- data_found = self.snapcmds[0][2]
859- self.assertEqual(mycfg, data_found['config'][name])
860-
861- def test_render_op_config_int(self):
862- # config entry for package can be a list, not a string blob
863- mycfg = 1
864- name = 'snapf1'
865- op = makeop('config', name, config=mycfg)
866- render_snap_op(**op)
867- data_found = self.snapcmds[0][2]
868- self.assertEqual(mycfg, data_found['config'][name])
869-
870- def test_render_long_configs_short(self):
871- # install a namespaced package should have un-namespaced config
872- mycfg = {'k1': 'k2'}
873- name = 'snapf1'
874- op = makeop('install', name + ".smoser", config=mycfg)
875- render_snap_op(**op)
876- data_found = self.snapcmds[0][2]
877- self.assertEqual(mycfg, data_found['config'][name])
878-
879- def test_render_does_not_pad_cfgfile(self):
880- # package_ops with cfgfile should not modify --file= content.
881- mydata = "foo1: bar1\nk: [l1, l2, l3]\n"
882- self.populate_tmp(
883- {"snapf1.snap": b"foo1", "snapf1.config": mydata.encode()})
884- ret = get_package_ops(
885- packages=[], configs={}, installed=[], fspath=self.tmp)
886- self.assertEqual(
887- ret,
888- [makeop_tmpd(self.tmp, 'install', 'snapf1', path="snapf1.snap",
889- cfgfile="snapf1.config")])
890-
891- # now the op was ok, but test that render didn't mess it up.
892- render_snap_op(**ret[0])
893- data_found = self.snapcmds[0][2]
894- # the data found gets loaded in the snapcmd interpretation
895- # so this comparison is a bit lossy, but input to snappy config
896- # is expected to be yaml loadable, so it should be OK.
897- self.assertEqual(yaml.safe_load(mydata), data_found)
898-
899-
900-class TestSnapConfig(FilesystemMockingTestCase):
901-
902- SYSTEM_USER_ASSERTION = textwrap.dedent("""
903- type: system-user
904- authority-id: LqvZQdfyfGlYvtep4W6Oj6pFXP9t1Ksp
905- brand-id: LqvZQdfyfGlYvtep4W6Oj6pFXP9t1Ksp
906- email: foo@bar.com
907- password: $6$E5YiAuMIPAwX58jG$miomhVNui/vf7f/3ctB/f0RWSKFxG0YXzrJ9rtJ1ikvzt
908- series:
909- - 16
910- since: 2016-09-10T16:34:00+03:00
911- until: 2017-11-10T16:34:00+03:00
912- username: baz
913- sign-key-sha3-384: RuVvnp4n52GilycjfbbTCI3_L8Y6QlIE75wxMc0KzGV3AUQqVd9GuXoj
914-
915- AcLBXAQAAQoABgUCV/UU1wAKCRBKnlMoJQLkZVeLD/9/+hIeVywtzsDA3oxl+P+u9D13y9s6svP
916- Jd6Wnf4FTw6sq1GjBE4ZA7lrwSaRCUJ9Vcsvf2q9OGPY7mOb2TBxaDe0PbUMjrSrqllSSQwhpNI
917- zG+NxkkKuxsUmLzFa+k9m6cyojNbw5LFhQZBQCGlr3JYqC0tIREq/UsZxj+90TUC87lDJwkU8GF
918- s4CR+rejZj4itIcDcVxCSnJH6hv6j2JrJskJmvObqTnoOlcab+JXdamXqbldSP3UIhWoyVjqzkj
919- +to7mXgx+cCUA9+ngNCcfUG+1huGGTWXPCYkZ78HvErcRlIdeo4d3xwtz1cl/w3vYnq9og1XwsP
920- Yfetr3boig2qs1Y+j/LpsfYBYncgWjeDfAB9ZZaqQz/oc8n87tIPZDJHrusTlBfop8CqcM4xsKS
921- d+wnEY8e/F24mdSOYmS1vQCIDiRU3MKb6x138Ud6oHXFlRBbBJqMMctPqWDunWzb5QJ7YR0I39q
922- BrnEqv5NE0G7w6HOJ1LSPG5Hae3P4T2ea+ATgkb03RPr3KnXnzXg4TtBbW1nytdlgoNc/BafE1H
923- f3NThcq9gwX4xWZ2PAWnqVPYdDMyCtzW3Ck+o6sIzx+dh4gDLPHIi/6TPe/pUuMop9CBpWwez7V
924- v1z+1+URx6Xlq3Jq18y5pZ6fY3IDJ6km2nQPMzcm4Q==""")
925-
926- ACCOUNT_ASSERTION = textwrap.dedent("""
927- type: account-key
928- authority-id: canonical
929- revision: 2
930- public-key-sha3-384: BWDEoaqyr25nF5SNCvEv2v7QnM9QsfCc0PBMYD_i2NGSQ32EF2d4D0
931- account-id: canonical
932- name: store
933- since: 2016-04-01T00:00:00.0Z
934- body-length: 717
935- sign-key-sha3-384: -CvQKAwRQ5h3Ffn10FILJoEZUXOv6km9FwA80-Rcj-f-6jadQ89VRswH
936-
937- AcbBTQRWhcGAARAA0KKYYQWuHOrsFVi4p4l7ZzSvX7kLgJFFeFgOkzdWKBTHEnsMKjl5mefFe9j
938- qe8NlmJdfY7BenP7XeBtwKp700H/t9lLrZbpTNAPHXYxEWFJp5bPqIcJYBZ+29oLVLN1Tc5X482
939- vCiDqL8+pPYqBrK2fNlyPlNNSum9wI70rDDL4r6FVvr+osTnGejibdV8JphWX+lrSQDnRSdM8KJ
940- UM43vTgLGTi9W54oRhsA2OFexRfRksTrnqGoonCjqX5wO3OFSaMDzMsO2MJ/hPfLgDqw53qjzuK
941- Iec9OL3k5basvu2cj5u9tKwVFDsCKK2GbKUsWWpx2KTpOifmhmiAbzkTHbH9KaoMS7p0kJwhTQG
942- o9aJ9VMTWHJc/NCBx7eu451u6d46sBPCXS/OMUh2766fQmoRtO1OwCTxsRKG2kkjbMn54UdFULl
943- VfzvyghMNRKIezsEkmM8wueTqGUGZWa6CEZqZKwhe/PROxOPYzqtDH18XZknbU1n5lNb7vNfem9
944- 2ai+3+JyFnW9UhfvpVF7gzAgdyCqNli4C6BIN43uwoS8HkykocZS/+Gv52aUQ/NZ8BKOHLw+7an
945- Q0o8W9ltSLZbEMxFIPSN0stiZlkXAp6DLyvh1Y4wXSynDjUondTpej2fSvSlCz/W5v5V7qA4nIc
946- vUvV7RjVzv17ut0AEQEAAQ==
947-
948- AcLDXAQAAQoABgUCV83k9QAKCRDUpVvql9g3IBT8IACKZ7XpiBZ3W4lqbPssY6On81WmxQLtvsM
949- WTp6zZpl/wWOSt2vMNUk9pvcmrNq1jG9CuhDfWFLGXEjcrrmVkN3YuCOajMSPFCGrxsIBLSRt/b
950- nrKykdLAAzMfG8rP1d82bjFFiIieE+urQ0Kcv09Jtdvavq3JT1Tek5mFyyfhHNlQEKOzWqmRWiL
951- 3c3VOZUs1ZD8TSlnuq/x+5T0X0YtOyGjSlVxk7UybbyMNd6MZfNaMpIG4x+mxD3KHFtBAC7O6kL
952- eX3i6j5nCY5UABfA3DZEAkWP4zlmdBEOvZ9t293NaDdOpzsUHRkoi0Zez/9BHQ/kwx/uNc2WqrY
953- inCmu16JGNeXqsyinnLl7Ghn2RwhvDMlLxF6RTx8xdx1yk6p3PBTwhZMUvuZGjUtN/AG8BmVJQ1
954- rsGSRkkSywvnhVJRB2sudnrMBmNS2goJbzSbmJnOlBrd2WsV0T9SgNMWZBiov3LvU4o2SmAb6b+
955- rYwh8H5QHcuuYJuxDjFhPswIp6Wes5T6hUicf3SWtObcDS4HSkVS4ImBjjX9YgCuFy7QdnooOWE
956- aPvkRw3XCVeYq0K6w9GRsk1YFErD4XmXXZjDYY650MX9v42Sz5MmphHV8jdIY5ssbadwFSe2rCQ
957- 6UX08zy7RsIb19hTndE6ncvSNDChUR9eEnCm73eYaWTWTnq1cxdVP/s52r8uss++OYOkPWqh5nO
958- haRn7INjH/yZX4qXjNXlTjo0PnHH0q08vNKDwLhxS+D9du+70FeacXFyLIbcWllSbJ7DmbumGpF
959- yYbtj3FDDPzachFQdIG3lSt+cSUGeyfSs6wVtc3cIPka/2Urx7RprfmoWSI6+a5NcLdj0u2z8O9
960- HxeIgxDpg/3gT8ZIuFKePMcLDM19Fh/p0ysCsX+84B9chNWtsMSmIaE57V+959MVtsLu7SLb9gi
961- skrju0pQCwsu2wHMLTNd1f3PTHmrr49hxetTus07HSQUApMtAGKzQilF5zqFjbyaTd4xgQbd+PK
962- CjFyzQTDOcUhXpuUGt/IzlqiFfsCsmbj2K4KdSNYMlqIgZ3Azu8KvZLIhsyN7v5vNIZSPfEbjde
963- ClU9r0VRiJmtYBUjcSghD9LWn+yRLwOxhfQVjm0cBwIt5R/yPF/qC76yIVuWUtM5Y2/zJR1J8OF
964- qWchvlImHtvDzS9FQeLyzJAOjvZ2CnWp2gILgUz0WQdOk1Dq8ax7KS9BQ42zxw9EZAEPw3PEFqR
965- IQsRTONp+iVS8YxSmoYZjDlCgRMWUmawez/Fv5b9Fb/XkO5Eq4e+KfrpUujXItaipb+tV8h5v3t
966- oG3Ie3WOHrVjCLXIdYslpL1O4nadqR6Xv58pHj6k""")
967-
968- test_assertions = [ACCOUNT_ASSERTION, SYSTEM_USER_ASSERTION]
969-
970- def setUp(self):
971- super(TestSnapConfig, self).setUp()
972- self.subp = util.subp
973- self.new_root = tempfile.mkdtemp()
974- self.addCleanup(shutil.rmtree, self.new_root)
975-
976- def _get_cloud(self, distro, metadata=None):
977- self.patchUtils(self.new_root)
978- paths = helpers.Paths({})
979- cls = distros.fetch(distro)
980- mydist = cls(distro, {}, paths)
981- myds = DataSourceNone.DataSourceNone({}, mydist, paths)
982- if metadata:
983- myds.metadata.update(metadata)
984- return cloud.Cloud(myds, paths, {}, mydist, None)
985-
986- @mock.patch('cloudinit.util.write_file')
987- @mock.patch('cloudinit.util.subp')
988- def test_snap_config_add_assertions(self, msubp, mwrite):
989- add_assertions(self.test_assertions)
990-
991- combined = "\n".join(self.test_assertions)
992- mwrite.assert_any_call(ASSERTIONS_FILE, combined.encode('utf-8'))
993- msubp.assert_called_with(['snap', 'ack', ASSERTIONS_FILE],
994- capture=True)
995-
996- def test_snap_config_add_assertions_empty(self):
997- self.assertRaises(ValueError, add_assertions, [])
998-
999- def test_add_assertions_nonlist(self):
1000- self.assertRaises(ValueError, add_assertions, {})
1001-
1002- @mock.patch('cloudinit.util.write_file')
1003- @mock.patch('cloudinit.util.subp')
1004- def test_snap_config_add_assertions_ack_fails(self, msubp, mwrite):
1005- msubp.side_effect = [util.ProcessExecutionError("Invalid assertion")]
1006- self.assertRaises(util.ProcessExecutionError, add_assertions,
1007- self.test_assertions)
1008-
1009- @mock.patch('cloudinit.config.cc_snap_config.add_assertions')
1010- @mock.patch('cloudinit.config.cc_snap_config.util')
1011- def test_snap_config_handle_no_config(self, mock_util, mock_add):
1012- cfg = {}
1013- cc = self._get_cloud('ubuntu')
1014- cc.distro = mock.MagicMock()
1015- cc.distro.name = 'ubuntu'
1016- mock_util.which.return_value = None
1017- snap_handle('snap_config', cfg, cc, LOG, None)
1018- mock_add.assert_not_called()
1019-
1020- def test_snap_config_add_snap_user_no_config(self):
1021- usercfg = add_snap_user(cfg=None)
1022- self.assertIsNone(usercfg)
1023-
1024- def test_snap_config_add_snap_user_not_dict(self):
1025- cfg = ['foobar']
1026- self.assertRaises(ValueError, add_snap_user, cfg)
1027-
1028- def test_snap_config_add_snap_user_no_email(self):
1029- cfg = {'assertions': [], 'known': True}
1030- usercfg = add_snap_user(cfg=cfg)
1031- self.assertIsNone(usercfg)
1032-
1033- @mock.patch('cloudinit.config.cc_snap_config.util')
1034- def test_snap_config_add_snap_user_email_only(self, mock_util):
1035- email = 'janet@planetjanet.org'
1036- cfg = {'email': email}
1037- mock_util.which.return_value = None
1038- mock_util.system_is_snappy.return_value = True
1039- mock_util.subp.side_effect = [
1040- ("false\n", ""), # snap managed
1041- ]
1042-
1043- usercfg = add_snap_user(cfg=cfg)
1044-
1045- self.assertEqual(usercfg, {'snapuser': email, 'known': False})
1046-
1047- @mock.patch('cloudinit.config.cc_snap_config.util')
1048- def test_snap_config_add_snap_user_email_known(self, mock_util):
1049- email = 'janet@planetjanet.org'
1050- known = True
1051- cfg = {'email': email, 'known': known}
1052- mock_util.which.return_value = None
1053- mock_util.system_is_snappy.return_value = True
1054- mock_util.subp.side_effect = [
1055- ("false\n", ""), # snap managed
1056- (self.SYSTEM_USER_ASSERTION, ""), # snap known system-user
1057- ]
1058-
1059- usercfg = add_snap_user(cfg=cfg)
1060-
1061- self.assertEqual(usercfg, {'snapuser': email, 'known': known})
1062-
1063- @mock.patch('cloudinit.config.cc_snap_config.add_assertions')
1064- @mock.patch('cloudinit.config.cc_snap_config.util')
1065- def test_snap_config_handle_system_not_snappy(self, mock_util, mock_add):
1066- cfg = {'snappy': {'assertions': self.test_assertions}}
1067- cc = self._get_cloud('ubuntu')
1068- cc.distro = mock.MagicMock()
1069- cc.distro.name = 'ubuntu'
1070- mock_util.which.return_value = None
1071- mock_util.system_is_snappy.return_value = False
1072-
1073- snap_handle('snap_config', cfg, cc, LOG, None)
1074-
1075- mock_add.assert_not_called()
1076-
1077- @mock.patch('cloudinit.config.cc_snap_config.add_assertions')
1078- @mock.patch('cloudinit.config.cc_snap_config.util')
1079- def test_snap_config_handle_snapuser(self, mock_util, mock_add):
1080- email = 'janet@planetjanet.org'
1081- cfg = {
1082- 'snappy': {
1083- 'assertions': self.test_assertions,
1084- 'email': email,
1085- }
1086- }
1087- cc = self._get_cloud('ubuntu')
1088- cc.distro = mock.MagicMock()
1089- cc.distro.name = 'ubuntu'
1090- mock_util.which.return_value = None
1091- mock_util.system_is_snappy.return_value = True
1092- mock_util.subp.side_effect = [
1093- ("false\n", ""), # snap managed
1094- ]
1095-
1096- snap_handle('snap_config', cfg, cc, LOG, None)
1097-
1098- mock_add.assert_called_with(self.test_assertions)
1099- usercfg = {'snapuser': email, 'known': False}
1100- cc.distro.create_user.assert_called_with(email, **usercfg)
1101-
1102- @mock.patch('cloudinit.config.cc_snap_config.add_assertions')
1103- @mock.patch('cloudinit.config.cc_snap_config.util')
1104- def test_snap_config_handle_snapuser_known(self, mock_util, mock_add):
1105- email = 'janet@planetjanet.org'
1106- cfg = {
1107- 'snappy': {
1108- 'assertions': self.test_assertions,
1109- 'email': email,
1110- 'known': True,
1111- }
1112- }
1113- cc = self._get_cloud('ubuntu')
1114- cc.distro = mock.MagicMock()
1115- cc.distro.name = 'ubuntu'
1116- mock_util.which.return_value = None
1117- mock_util.system_is_snappy.return_value = True
1118- mock_util.subp.side_effect = [
1119- ("false\n", ""), # snap managed
1120- (self.SYSTEM_USER_ASSERTION, ""), # snap known system-user
1121- ]
1122-
1123- snap_handle('snap_config', cfg, cc, LOG, None)
1124-
1125- mock_add.assert_called_with(self.test_assertions)
1126- usercfg = {'snapuser': email, 'known': True}
1127- cc.distro.create_user.assert_called_with(email, **usercfg)
1128-
1129- @mock.patch('cloudinit.config.cc_snap_config.add_assertions')
1130- @mock.patch('cloudinit.config.cc_snap_config.util')
1131- def test_snap_config_handle_snapuser_known_managed(self, mock_util,
1132- mock_add):
1133- email = 'janet@planetjanet.org'
1134- cfg = {
1135- 'snappy': {
1136- 'assertions': self.test_assertions,
1137- 'email': email,
1138- 'known': True,
1139- }
1140- }
1141- cc = self._get_cloud('ubuntu')
1142- cc.distro = mock.MagicMock()
1143- cc.distro.name = 'ubuntu'
1144- mock_util.which.return_value = None
1145- mock_util.system_is_snappy.return_value = True
1146- mock_util.subp.side_effect = [
1147- ("true\n", ""), # snap managed
1148- ]
1149-
1150- snap_handle('snap_config', cfg, cc, LOG, None)
1151-
1152- mock_add.assert_called_with(self.test_assertions)
1153- cc.distro.create_user.assert_not_called()
1154-
1155- @mock.patch('cloudinit.config.cc_snap_config.add_assertions')
1156- @mock.patch('cloudinit.config.cc_snap_config.util')
1157- def test_snap_config_handle_snapuser_known_no_assertion(self, mock_util,
1158- mock_add):
1159- email = 'janet@planetjanet.org'
1160- cfg = {
1161- 'snappy': {
1162- 'assertions': [self.ACCOUNT_ASSERTION],
1163- 'email': email,
1164- 'known': True,
1165- }
1166- }
1167- cc = self._get_cloud('ubuntu')
1168- cc.distro = mock.MagicMock()
1169- cc.distro.name = 'ubuntu'
1170- mock_util.which.return_value = None
1171- mock_util.system_is_snappy.return_value = True
1172- mock_util.subp.side_effect = [
1173- ("true\n", ""), # snap managed
1174- ("", ""), # snap known system-user
1175- ]
1176-
1177- snap_handle('snap_config', cfg, cc, LOG, None)
1178-
1179- mock_add.assert_called_with([self.ACCOUNT_ASSERTION])
1180- cc.distro.create_user.assert_not_called()
1181-
1182-
1183-def makeop_tmpd(tmpd, op, name, config=None, path=None, cfgfile=None):
1184- if cfgfile:
1185- cfgfile = os.path.sep.join([tmpd, cfgfile])
1186- if path:
1187- path = os.path.sep.join([tmpd, path])
1188- return(makeop(op=op, name=name, config=config, path=path, cfgfile=cfgfile))
1189-
1190-
1191-def apply_patches(patches):
1192- ret = []
1193- for (ref, name, replace) in patches:
1194- if replace is None:
1195- continue
1196- orig = getattr(ref, name)
1197- setattr(ref, name, replace)
1198- ret.append((ref, name, orig))
1199- return ret
1200-
1201-# vi: ts=4 expandtab

Subscribers

People subscribed via source and target branches