some comments
Diff comments: > diff --git a/cloudinit/config/cc_snap.py b/cloudinit/config/cc_snap.py > new file mode 100644 > index 0000000..1f94079 > --- /dev/null > +++ b/cloudinit/config/cc_snap.py > @@ -0,0 +1,275 @@ > +# Copyright (C) 2018 Canonical Ltd. > +# > +# This file is part of cloud-init. See LICENSE file for license information. > + > +"""Snap: Install, configure and manage snapd and snap packages.""" > + > +from textwrap import dedent > + > +from cloudinit.config.schema import ( > + get_schema_doc, validate_cloudconfig_schema) > +from cloudinit.settings import PER_INSTANCE > +from cloudinit import temp_utils > +from cloudinit import util > + > +distros = ['ubuntu'] > +frequency = PER_INSTANCE > + > + > +schema = { > + 'id': 'cc_snap', > + 'name': 'Snap', > + 'title': 'Install, configure and manage snapd and snap packages', > + 'description': dedent("""\ > + This module provides a simple configuration namespace in cloud-init > to > + both setup snapd and install snaps. > + > + .. note:: > + Both ``assertions`` and ``commands`` values can be either a > + dictionary or a list. If these configs are provided as a > + dictionary, the keys of that list are only used to order the > + execution of the assertions or commands and the dictionary is > + merged with any vendor-data snap configuration provided. If a > list > + is provided by the user, any vendor-data snap configuration is > + ignored. > + > + The ``assertions`` configuration option is a dictionary or list of > + properly-signed snap assertions which will run before any snap > + ``commands``. They will be added to snapd's assertion database by > + invoking ``snap ack <aggregate_assertion_file>``. > + > + Snap ``commands`` is a dictionary or list of individual snap > + commands to run on the target system. These commands can be used to > + create snap users, install snaps and provide snap configuration. > + > + .. note:: > + If 'side-loading' private/unpublished snaps on an instance, it is > + best to create a snap seed directory and seed.yaml manifest in > + **/var/lib/snapd/seed/** which snapd automatically installs on > + startup. > + > + **Development only**: The ``squashfuse_in_container`` boolean can be > + set true to install squashfuse package when in a container to enable > + snap installs. Default is false. > + """), > + 'distros': distros, > + 'examples': [dedent("""\ > + snap: > + assertions: > + 00: | > + signed_assertion_blob_here > + 02: | > + signed_assertion_blob_here > + commands: > + 00: snap create-user --sudoer --known <snap-user>@mydomain.com > + 01: snap install canonical-livepatch > + 02: canonical-livepatch enable <AUTH_TOKEN> > + """), dedent("""\ > + # LXC-based containers require squashfuse before snaps can be > installed > + snap: > + commands: > + 00: apt-get install squashfuse -y this is wrong. that would execute snap apt-get install ... > + 11: snap install emoj > + > + """), dedent("""\ > + # Convenience: the snap command can be ommited when specifying > commands > + # as a list and 'snap' will automatically be prepended. > + # The following commands are equivalent: > + snap: > + commands: > + 00: ['install', 'vlc'] > + 01: ['snap', 'install', 'vlc'] > + 02: snap install vlc > + 03: 'snap install vlc' > + """)], > + 'frequency': PER_INSTANCE, > + 'type': 'object', > + 'properties': { > + 'snap': { > + 'type': 'object', > + 'properties': { > + 'assertions': { > + 'type': ['object', 'array'], # Array of strings or dict > + 'items': {'type': 'string'}, > + 'additionalItems': False, # Reject items non-string > + 'minItems': 1, > + 'minProperties': 1, > + 'uniqueItems': True > + }, > + 'commands': { > + 'type': ['object', 'array'], # Array of strings or dict > + 'items': { > + 'oneOf': [ > + {'type': 'array', 'items': {'type': 'string'}}, > + {'type': 'string'}] > + }, > + 'additionalItems': False, # Reject non-string & non-list > + 'minItems': 1, > + 'minProperties': 1, > + 'uniqueItems': True > + }, > + 'squashfuse_in_container': { > + 'type': 'boolean' > + } > + }, > + 'additionalProperties': False, # Reject keys not in schema > + 'required': [], > + 'minProperties': 1 > + } > + } > +} > + > +# TODO schema for 'assertions' and 'commands' are too permissive at the > moment. > +# Once python-jsonschema supports schema draft 6 add support for arbitrary > +# object keys with 'patternProperties' constraint to validate string values. > + > +__doc__ = get_schema_doc(schema) # Supplement python help() > + > +SNAP_CMD = "snap" > +ASSERTIONS_FILE = "/var/lib/cloud/instance/snapd.assertions" > + > + > +def add_assertions(assertions, log): > + """Import list of assertions. > + > + Import assertions by concatenating each assertion into a > + string separated by a '\n'. Write this string to a instance file and > + then invoke `snap ack /path/to/file` and check for errors. > + If snap exits 0, then all assertions are imported. > + """ > + if not assertions: > + return > + log.debug('Importing user-provided snap assertions') > + if isinstance(assertions, dict): > + assertions = assertions.values() > + elif not isinstance(assertions, list): > + raise ValueError( > + 'assertion parameter was not a list or dict: > {assertions}'.format( > + assertions=assertions)) > + > + snap_cmd = [SNAP_CMD, 'ack'] > + combined = "\n".join(assertions) > + > + for asrt in assertions: > + log.debug('Snap acking: %s', asrt.split('\n')[0:2]) > + > + util.write_file(ASSERTIONS_FILE, combined.encode('utf-8')) > + util.subp(snap_cmd + [ASSERTIONS_FILE], capture=True) > + > + > +def prepend_snap_commands(commands, log): > + """Ensure user-provided commands start with SNAP_CMD, warn otherwise. > + > + Each command is either a list or string. Perform the following: > + - When the command is a list, pop the first element if it is None > + - When the command is a list, insert SNAP_CMD as the first element if > + not present. > + - When the command is a string containing a non-snap command, warn. > + > + Support cut-n-paste snap command sets from public snappy documentation. > + Allow flexibility to provide non-snap environment/config setup if > neeeded. > + > + @commands: List of commands. Each command element is a list or string. > + @log: A logger instance for emitting logs. > + > + @return: List of 'fixed up' snap commands. > + @raise: ValueError on invalid config item type. > + """ > + warnings = [] > + errors = [] > + fixed_commands = [] > + for command in commands: > + if isinstance(command, list): > + if command[0] is None: # Avoid warnings by specifying None > + command = command[1:] > + elif command[0] not in SNAP_CMD: # Automatically prepend > SNAP_CMD > + command.insert(0, SNAP_CMD) > + elif isinstance(command, str): > + if not command.startswith('%s ' % SNAP_CMD): > + warnings.append(command) > + else: > + errors.append(str(command)) > + continue > + fixed_commands.append(command) > + > + if warnings: > + log.warning( > + 'Non-snap commands in snap config:\n%s', > + '\n'.join(warnings)) > + if errors: > + raise ValueError( > + 'Invalid snap config.' > + ' These commands are not a string or list:\n' + > '\n'.join(errors)) > + return fixed_commands > + > + > +def run_commands(commands, log): > + """Run the provided commands provided in snap:commands configuration. > + > + Commands are aggregated,shellified and written to a temporary script > file > + which is then executed. > + > + @param commands: A list or dict containing commands to run. Keys of a > + dict will be used to order the commands provided as dict values. > + """ > + if not commands: > + return > + log.debug('Running user-provided snap commands') > + if isinstance(commands, dict): > + # Sort commands based on arbitrary dictionary key 'arbitrary' seems a strange word here. probably they're not arbitrary, the user selected them. > + commands = [v for _, v in sorted(commands.items())] > + elif not isinstance(commands, list): > + raise ValueError( > + 'commands parameter was not a list or dict: {commands}'.format( > + commands=commands)) > + > + fixed_snap_commands = prepend_snap_commands(commands, log) > + > + with temp_utils.ExtendedTemporaryFile(suffix=".sh") as tmpf: > + try: > + content = util.shellify(fixed_snap_commands) > + tmpf.write(util.encode_text(content)) > + tmpf.flush() > + except Exception as e: > + util.logexc(log, "Failed to shellify snap:commands: %s", str(e)) > + raise > + > + try: > + cmd = ['/bin/sh', tmpf.name] > + util.subp(cmd, capture=False) > + except Exception: > + util.logexc(log, 'Failed to run snap commands') > + raise > + > + > +def maybe_install_squashfuse(cloud, log): > + """Install squashfuse if we are in a container.""" > + if not util.is_container(): > + return > + log.warning('Snaps in containers require squashfuse per lp:1628289') because of the way they fixed bug 1628289 the warning isn't correct. lets drop the warning i guess. since we expect this to be fixed shortly and don't want to have to come back to remove invalid warning spam. also, LP: #XXXXXX not lp:xxxxxx > + try: > + cloud.distro.update_package_sources() > + except Exception as e: > + util.logexc(log, "Package update failed") > + raise > + try: > + cloud.distro.install_packages(['squashfuse']) > + except Exception as e: > + util.logexc(log, "Failed to install squashfuse") > + raise > + > + > +def handle(name, cfg, cloud, log, args): > + cfgin = cfg.get('snap', {}) > + if not cfgin: > + log.debug(("Skipping module named %s," > + " no 'snap' key in configuration"), name) > + return > + > + validate_cloudconfig_schema(cfg, schema) > + if util.is_true(cfgin.get('squashfuse_in_container', False)): > + maybe_install_squashfuse(cloud, log) > + add_assertions(cfgin.get('assertions', []), log=log) > + run_commands(cfgin.get('commands', []), log=log) > + > +# vi: ts=4 expandtab > diff --git a/config/cloud.cfg.tmpl b/config/cloud.cfg.tmpl > index fad1184..42834a0 100644 > --- a/config/cloud.cfg.tmpl > +++ b/config/cloud.cfg.tmpl > @@ -72,6 +72,7 @@ cloud_config_modules: > # Emit the cloud config ready event > # this can be used by upstart jobs for 'start on cloud-config'. > - emit_upstart > + - snap > - snap_config you left snap_config by design ? > {% endif %} > - ssh-import-id -- https://code.launchpad.net/~chad.smith/cloud-init/+git/cloud-init/+merge/338366 Your team cloud-init commiters is requested to review the proposed merge of ~chad.smith/cloud-init:feature/snap-module into cloud-init:master. _______________________________________________ Mailing list: https://launchpad.net/~cloud-init-dev Post to : cloud-init-dev@lists.launchpad.net Unsubscribe : https://launchpad.net/~cloud-init-dev More help : https://help.launchpad.net/ListHelp