Blame cloudinit/config/cc_snap.py

Packit Service a04d08
# Copyright (C) 2018 Canonical Ltd.
Packit Service a04d08
#
Packit Service a04d08
# This file is part of cloud-init. See LICENSE file for license information.
Packit Service a04d08
Packit Service a04d08
"""Snap: Install, configure and manage snapd and snap packages."""
Packit Service a04d08
Packit Service a04d08
import sys
Packit Service a04d08
from textwrap import dedent
Packit Service a04d08
Packit Service a04d08
from cloudinit import log as logging
Packit Service a04d08
from cloudinit.config.schema import (
Packit Service a04d08
    get_schema_doc, validate_cloudconfig_schema)
Packit Service a04d08
from cloudinit.settings import PER_INSTANCE
Packit Service a04d08
from cloudinit.subp import prepend_base_command
Packit Service 751c4a
from cloudinit import subp
Packit Service a04d08
from cloudinit import util
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
distros = ['ubuntu']
Packit Service a04d08
frequency = PER_INSTANCE
Packit Service a04d08
Packit Service a04d08
LOG = logging.getLogger(__name__)
Packit Service a04d08
Packit Service a04d08
schema = {
Packit Service a04d08
    'id': 'cc_snap',
Packit Service a04d08
    'name': 'Snap',
Packit Service a04d08
    'title': 'Install, configure and manage snapd and snap packages',
Packit Service a04d08
    'description': dedent("""\
Packit Service a04d08
        This module provides a simple configuration namespace in cloud-init to
Packit Service a04d08
        both setup snapd and install snaps.
Packit Service a04d08
Packit Service a04d08
        .. note::
Packit Service a04d08
            Both ``assertions`` and ``commands`` values can be either a
Packit Service a04d08
            dictionary or a list. If these configs are provided as a
Packit Service a04d08
            dictionary, the keys are only used to order the execution of the
Packit Service a04d08
            assertions or commands and the dictionary is merged with any
Packit Service a04d08
            vendor-data snap configuration provided. If a list is provided by
Packit Service a04d08
            the user instead of a dict, any vendor-data snap configuration is
Packit Service a04d08
            ignored.
Packit Service a04d08
Packit Service a04d08
        The ``assertions`` configuration option is a dictionary or list of
Packit Service a04d08
        properly-signed snap assertions which will run before any snap
Packit Service a04d08
        ``commands``. They will be added to snapd's assertion database by
Packit Service a04d08
        invoking ``snap ack <aggregate_assertion_file>``.
Packit Service a04d08
Packit Service a04d08
        Snap ``commands`` is a dictionary or list of individual snap
Packit Service a04d08
        commands to run on the target system. These commands can be used to
Packit Service a04d08
        create snap users, install snaps and provide snap configuration.
Packit Service a04d08
Packit Service a04d08
        .. note::
Packit Service a04d08
            If 'side-loading' private/unpublished snaps on an instance, it is
Packit Service a04d08
            best to create a snap seed directory and seed.yaml manifest in
Packit Service a04d08
            **/var/lib/snapd/seed/** which snapd automatically installs on
Packit Service a04d08
            startup.
Packit Service a04d08
Packit Service a04d08
        **Development only**: The ``squashfuse_in_container`` boolean can be
Packit Service a04d08
        set true to install squashfuse package when in a container to enable
Packit Service a04d08
        snap installs. Default is false.
Packit Service a04d08
        """),
Packit Service a04d08
    'distros': distros,
Packit Service a04d08
    'examples': [dedent("""\
Packit Service a04d08
        snap:
Packit Service a04d08
            assertions:
Packit Service a04d08
              00: |
Packit Service 751c4a
                signed_assertion_blob_here
Packit Service a04d08
              02: |
Packit Service 751c4a
                signed_assertion_blob_here
Packit Service a04d08
            commands:
Packit Service a04d08
              00: snap create-user --sudoer --known <snap-user>@mydomain.com
Packit Service a04d08
              01: snap install canonical-livepatch
Packit Service a04d08
              02: canonical-livepatch enable <AUTH_TOKEN>
Packit Service a04d08
    """), dedent("""\
Packit Service a04d08
        # LXC-based containers require squashfuse before snaps can be installed
Packit Service a04d08
        snap:
Packit Service a04d08
            commands:
Packit Service a04d08
                00: apt-get install squashfuse -y
Packit Service a04d08
                11: snap install emoj
Packit Service a04d08
Packit Service a04d08
    """), dedent("""\
Packit Service a04d08
        # Convenience: the snap command can be omitted when specifying commands
Packit Service a04d08
        # as a list and 'snap' will automatically be prepended.
Packit Service a04d08
        # The following commands are equivalent:
Packit Service a04d08
        snap:
Packit Service a04d08
            commands:
Packit Service a04d08
                00: ['install', 'vlc']
Packit Service a04d08
                01: ['snap', 'install', 'vlc']
Packit Service a04d08
                02: snap install vlc
Packit Service a04d08
                03: 'snap install vlc'
Packit Service 751c4a
    """), dedent("""\
Packit Service 751c4a
        # You can use a list of commands
Packit Service 751c4a
        snap:
Packit Service 751c4a
            commands:
Packit Service 751c4a
                - ['install', 'vlc']
Packit Service 751c4a
                - ['snap', 'install', 'vlc']
Packit Service 751c4a
                - snap install vlc
Packit Service 751c4a
                - 'snap install vlc'
Packit Service 751c4a
    """), dedent("""\
Packit Service 751c4a
        # You can use a list of assertions
Packit Service 751c4a
        snap:
Packit Service 751c4a
            assertions:
Packit Service 751c4a
                - signed_assertion_blob_here
Packit Service 751c4a
                - |
Packit Service 751c4a
                    signed_assertion_blob_here
Packit Service a04d08
    """)],
Packit Service a04d08
    'frequency': PER_INSTANCE,
Packit Service a04d08
    'type': 'object',
Packit Service a04d08
    'properties': {
Packit Service a04d08
        'snap': {
Packit Service a04d08
            'type': 'object',
Packit Service a04d08
            'properties': {
Packit Service a04d08
                'assertions': {
Packit Service a04d08
                    'type': ['object', 'array'],  # Array of strings or dict
Packit Service a04d08
                    'items': {'type': 'string'},
Packit Service a04d08
                    'additionalItems': False,  # Reject items non-string
Packit Service a04d08
                    'minItems': 1,
Packit Service a04d08
                    'minProperties': 1,
Packit Service 751c4a
                    'uniqueItems': True,
Packit Service 751c4a
                    'additionalProperties': {'type': 'string'},
Packit Service a04d08
                },
Packit Service a04d08
                'commands': {
Packit Service a04d08
                    'type': ['object', 'array'],  # Array of strings or dict
Packit Service a04d08
                    'items': {
Packit Service a04d08
                        'oneOf': [
Packit Service a04d08
                            {'type': 'array', 'items': {'type': 'string'}},
Packit Service a04d08
                            {'type': 'string'}]
Packit Service a04d08
                    },
Packit Service a04d08
                    'additionalItems': False,  # Reject non-string & non-list
Packit Service a04d08
                    'minItems': 1,
Packit Service a04d08
                    'minProperties': 1,
Packit Service 751c4a
                    'additionalProperties': {
Packit Service 751c4a
                        'oneOf': [
Packit Service 751c4a
                            {'type': 'string'},
Packit Service 751c4a
                            {'type': 'array', 'items': {'type': 'string'}},
Packit Service 751c4a
                        ],
Packit Service 751c4a
                    },
Packit Service a04d08
                },
Packit Service a04d08
                'squashfuse_in_container': {
Packit Service a04d08
                    'type': 'boolean'
Packit Service a04d08
                }
Packit Service a04d08
            },
Packit Service a04d08
            'additionalProperties': False,  # Reject keys not in schema
Packit Service a04d08
            'required': [],
Packit Service a04d08
            'minProperties': 1
Packit Service a04d08
        }
Packit Service a04d08
    }
Packit Service a04d08
}
Packit Service a04d08
Packit Service a04d08
__doc__ = get_schema_doc(schema)  # Supplement python help()
Packit Service a04d08
Packit Service a04d08
SNAP_CMD = "snap"
Packit Service a04d08
ASSERTIONS_FILE = "/var/lib/cloud/instance/snapd.assertions"
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def add_assertions(assertions):
Packit Service a04d08
    """Import list of assertions.
Packit Service a04d08
Packit Service a04d08
    Import assertions by concatenating each assertion into a
Packit Service a04d08
    string separated by a '\n'.  Write this string to a instance file and
Packit Service a04d08
    then invoke `snap ack /path/to/file` and check for errors.
Packit Service a04d08
    If snap exits 0, then all assertions are imported.
Packit Service a04d08
    """
Packit Service a04d08
    if not assertions:
Packit Service a04d08
        return
Packit Service a04d08
    LOG.debug('Importing user-provided snap assertions')
Packit Service a04d08
    if isinstance(assertions, dict):
Packit Service a04d08
        assertions = assertions.values()
Packit Service a04d08
    elif not isinstance(assertions, list):
Packit Service a04d08
        raise TypeError(
Packit Service a04d08
            'assertion parameter was not a list or dict: {assertions}'.format(
Packit Service a04d08
                assertions=assertions))
Packit Service a04d08
Packit Service a04d08
    snap_cmd = [SNAP_CMD, 'ack']
Packit Service a04d08
    combined = "\n".join(assertions)
Packit Service a04d08
Packit Service a04d08
    for asrt in assertions:
Packit Service a04d08
        LOG.debug('Snap acking: %s', asrt.split('\n')[0:2])
Packit Service a04d08
Packit Service a04d08
    util.write_file(ASSERTIONS_FILE, combined.encode('utf-8'))
Packit Service 751c4a
    subp.subp(snap_cmd + [ASSERTIONS_FILE], capture=True)
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def run_commands(commands):
Packit Service a04d08
    """Run the provided commands provided in snap:commands configuration.
Packit Service a04d08
Packit Service a04d08
     Commands are run individually. Any errors are collected and reported
Packit Service a04d08
     after attempting all commands.
Packit Service a04d08
Packit Service a04d08
     @param commands: A list or dict containing commands to run. Keys of a
Packit Service a04d08
         dict will be used to order the commands provided as dict values.
Packit Service a04d08
     """
Packit Service a04d08
    if not commands:
Packit Service a04d08
        return
Packit Service a04d08
    LOG.debug('Running user-provided snap commands')
Packit Service a04d08
    if isinstance(commands, dict):
Packit Service a04d08
        # Sort commands based on dictionary key
Packit Service a04d08
        commands = [v for _, v in sorted(commands.items())]
Packit Service a04d08
    elif not isinstance(commands, list):
Packit Service a04d08
        raise TypeError(
Packit Service a04d08
            'commands parameter was not a list or dict: {commands}'.format(
Packit Service a04d08
                commands=commands))
Packit Service a04d08
Packit Service a04d08
    fixed_snap_commands = prepend_base_command('snap', commands)
Packit Service a04d08
Packit Service a04d08
    cmd_failures = []
Packit Service a04d08
    for command in fixed_snap_commands:
Packit Service a04d08
        shell = isinstance(command, str)
Packit Service a04d08
        try:
Packit Service 751c4a
            subp.subp(command, shell=shell, status_cb=sys.stderr.write)
Packit Service 751c4a
        except subp.ProcessExecutionError as e:
Packit Service a04d08
            cmd_failures.append(str(e))
Packit Service a04d08
    if cmd_failures:
Packit Service a04d08
        msg = 'Failures running snap commands:\n{cmd_failures}'.format(
Packit Service a04d08
            cmd_failures=cmd_failures)
Packit Service a04d08
        util.logexc(LOG, msg)
Packit Service a04d08
        raise RuntimeError(msg)
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
# RELEASE_BLOCKER: Once LP: #1628289 is released on xenial, drop this function.
Packit Service a04d08
def maybe_install_squashfuse(cloud):
Packit Service a04d08
    """Install squashfuse if we are in a container."""
Packit Service a04d08
    if not util.is_container():
Packit Service a04d08
        return
Packit Service a04d08
    try:
Packit Service a04d08
        cloud.distro.update_package_sources()
Packit Service a04d08
    except Exception:
Packit Service a04d08
        util.logexc(LOG, "Package update failed")
Packit Service a04d08
        raise
Packit Service a04d08
    try:
Packit Service a04d08
        cloud.distro.install_packages(['squashfuse'])
Packit Service a04d08
    except Exception:
Packit Service a04d08
        util.logexc(LOG, "Failed to install squashfuse")
Packit Service a04d08
        raise
Packit Service a04d08
Packit Service a04d08
Packit Service a04d08
def handle(name, cfg, cloud, log, args):
Packit Service a04d08
    cfgin = cfg.get('snap', {})
Packit Service a04d08
    if not cfgin:
Packit Service a04d08
        LOG.debug(("Skipping module named %s,"
Packit Service a04d08
                   " no 'snap' key in configuration"), name)
Packit Service a04d08
        return
Packit Service a04d08
Packit Service a04d08
    validate_cloudconfig_schema(cfg, schema)
Packit Service a04d08
    if util.is_true(cfgin.get('squashfuse_in_container', False)):
Packit Service a04d08
        maybe_install_squashfuse(cloud)
Packit Service a04d08
    add_assertions(cfgin.get('assertions', []))
Packit Service a04d08
    run_commands(cfgin.get('commands', []))
Packit Service a04d08
Packit Service a04d08
# vi: ts=4 expandtab