Blame cloudinit/config/cc_snappy.py

Packit Service 11b429
# This file is part of cloud-init. See LICENSE file for license information.
Packit Service 11b429
Packit Service 11b429
# RELEASE_BLOCKER: Remove this deprecated module in 18.3
Packit Service 11b429
"""
Packit Service 11b429
Snappy
Packit Service 11b429
------
Packit Service 11b429
**Summary:** snappy modules allows configuration of snappy.
Packit Service 11b429
Packit Service 11b429
**Deprecated**: Use :ref:`snap` module instead. This module will not exist
Packit Service 11b429
in cloud-init 18.3.
Packit Service 11b429
Packit Service 11b429
The below example config config would install ``etcd``, and then install
Packit Service 11b429
``pkg2.smoser`` with a ``<config-file>`` argument where ``config-file`` has
Packit Service 11b429
``config-blob`` inside it. If ``pkgname`` is installed already, then
Packit Service 11b429
``snappy config pkgname <file>``
Packit Service 11b429
will be called where ``file`` has ``pkgname-config-blob`` as its content.
Packit Service 11b429
Packit Service 11b429
Entries in ``config`` can be namespaced or non-namespaced for a package.
Packit Service 11b429
In either case, the config provided to snappy command is non-namespaced.
Packit Service 11b429
The package name is provided as it appears.
Packit Service 11b429
Packit Service 11b429
If ``packages_dir`` has files in it that end in ``.snap``, then they are
Packit Service 11b429
installed.  Given 3 files:
Packit Service 11b429
Packit Service 11b429
  - <packages_dir>/foo.snap
Packit Service 11b429
  - <packages_dir>/foo.config
Packit Service 11b429
  - <packages_dir>/bar.snap
Packit Service 11b429
Packit Service 11b429
cloud-init will invoke:
Packit Service 11b429
Packit Service 11b429
  - snappy install <packages_dir>/foo.snap <packages_dir>/foo.config
Packit Service 11b429
  - snappy install <packages_dir>/bar.snap
Packit Service 11b429
Packit Service 11b429
.. note::
Packit Service 11b429
    that if provided a ``config`` entry for ``ubuntu-core``, then
Packit Service 11b429
    cloud-init will invoke: snappy config ubuntu-core <config>
Packit Service 11b429
    Allowing you to configure ubuntu-core in this way.
Packit Service 11b429
Packit Service 11b429
The ``ssh_enabled`` key controls the system's ssh service. The default value
Packit Service 11b429
is ``auto``. Options are:
Packit Service 11b429
Packit Service 11b429
  - **True:** enable ssh service
Packit Service 11b429
  - **False:** disable ssh service
Packit Service 11b429
  - **auto:** enable ssh service if either ssh keys have been provided
Packit Service 11b429
    or user has requested password authentication (ssh_pwauth).
Packit Service 11b429
Packit Service 11b429
**Internal name:** ``cc_snappy``
Packit Service 11b429
Packit Service 11b429
**Module frequency:** per instance
Packit Service 11b429
Packit Service 11b429
**Supported distros:** ubuntu
Packit Service 11b429
Packit Service 11b429
**Config keys**::
Packit Service 11b429
Packit Service 11b429
    #cloud-config
Packit Service 11b429
    snappy:
Packit Service 11b429
        system_snappy: auto
Packit Service 11b429
        ssh_enabled: auto
Packit Service 11b429
        packages: [etcd, pkg2.smoser]
Packit Service 11b429
        config:
Packit Service 11b429
            pkgname:
Packit Service 11b429
                key2: value2
Packit Service 11b429
            pkg2:
Packit Service 11b429
                key1: value1
Packit Service 11b429
        packages_dir: '/writable/user-data/cloud-init/snaps'
Packit Service 11b429
"""
Packit Service 11b429
Packit Service 11b429
from cloudinit import log as logging
Packit Service 11b429
from cloudinit.settings import PER_INSTANCE
Packit Service 11b429
from cloudinit import temp_utils
Packit Service 11b429
from cloudinit import safeyaml
Packit Service 11b429
from cloudinit import util
Packit Service 11b429
Packit Service 11b429
import glob
Packit Service 11b429
import os
Packit Service 11b429
Packit Service 11b429
LOG = logging.getLogger(__name__)
Packit Service 11b429
Packit Service 11b429
frequency = PER_INSTANCE
Packit Service 11b429
SNAPPY_CMD = "snappy"
Packit Service 11b429
NAMESPACE_DELIM = '.'
Packit Service 11b429
Packit Service 11b429
BUILTIN_CFG = {
Packit Service 11b429
    'packages': [],
Packit Service 11b429
    'packages_dir': '/writable/user-data/cloud-init/snaps',
Packit Service 11b429
    'ssh_enabled': "auto",
Packit Service 11b429
    'system_snappy': "auto",
Packit Service 11b429
    'config': {},
Packit Service 11b429
}
Packit Service 11b429
Packit Service 11b429
distros = ['ubuntu']
Packit Service 11b429
Packit Service 11b429
Packit Service 11b429
def parse_filename(fname):
Packit Service 11b429
    fname = os.path.basename(fname)
Packit Service 11b429
    fname_noext = fname.rpartition(".")[0]
Packit Service 11b429
    name = fname_noext.partition("_")[0]
Packit Service 11b429
    shortname = name.partition(".")[0]
Packit Service 11b429
    return(name, shortname, fname_noext)
Packit Service 11b429
Packit Service 11b429
Packit Service 11b429
def get_fs_package_ops(fspath):
Packit Service 11b429
    if not fspath:
Packit Service 11b429
        return []
Packit Service 11b429
    ops = []
Packit Service 11b429
    for snapfile in sorted(glob.glob(os.path.sep.join([fspath, '*.snap']))):
Packit Service 11b429
        (name, shortname, fname_noext) = parse_filename(snapfile)
Packit Service 11b429
        cfg = None
Packit Service 11b429
        for cand in (fname_noext, name, shortname):
Packit Service 11b429
            fpcand = os.path.sep.join([fspath, cand]) + ".config"
Packit Service 11b429
            if os.path.isfile(fpcand):
Packit Service 11b429
                cfg = fpcand
Packit Service 11b429
                break
Packit Service 11b429
        ops.append(makeop('install', name, config=None,
Packit Service 11b429
                   path=snapfile, cfgfile=cfg))
Packit Service 11b429
    return ops
Packit Service 11b429
Packit Service 11b429
Packit Service 11b429
def makeop(op, name, config=None, path=None, cfgfile=None):
Packit Service 11b429
    return({'op': op, 'name': name, 'config': config, 'path': path,
Packit Service 11b429
            'cfgfile': cfgfile})
Packit Service 11b429
Packit Service 11b429
Packit Service 11b429
def get_package_config(configs, name):
Packit Service 11b429
    # load the package's config from the configs dict.
Packit Service 11b429
    # prefer full-name entry (config-example.canonical)
Packit Service 11b429
    # over short name entry (config-example)
Packit Service 11b429
    if name in configs:
Packit Service 11b429
        return configs[name]
Packit Service 11b429
    return configs.get(name.partition(NAMESPACE_DELIM)[0])
Packit Service 11b429
Packit Service 11b429
Packit Service 11b429
def get_package_ops(packages, configs, installed=None, fspath=None):
Packit Service 11b429
    # get the install an config operations that should be done
Packit Service 11b429
    if installed is None:
Packit Service 11b429
        installed = read_installed_packages()
Packit Service 11b429
    short_installed = [p.partition(NAMESPACE_DELIM)[0] for p in installed]
Packit Service 11b429
Packit Service 11b429
    if not packages:
Packit Service 11b429
        packages = []
Packit Service 11b429
    if not configs:
Packit Service 11b429
        configs = {}
Packit Service 11b429
Packit Service 11b429
    ops = []
Packit Service 11b429
    ops += get_fs_package_ops(fspath)
Packit Service 11b429
Packit Service 11b429
    for name in packages:
Packit Service 11b429
        ops.append(makeop('install', name, get_package_config(configs, name)))
Packit Service 11b429
Packit Service 11b429
    to_install = [f['name'] for f in ops]
Packit Service 11b429
    short_to_install = [f['name'].partition(NAMESPACE_DELIM)[0] for f in ops]
Packit Service 11b429
Packit Service 11b429
    for name in configs:
Packit Service 11b429
        if name in to_install:
Packit Service 11b429
            continue
Packit Service 11b429
        shortname = name.partition(NAMESPACE_DELIM)[0]
Packit Service 11b429
        if shortname in short_to_install:
Packit Service 11b429
            continue
Packit Service 11b429
        if name in installed or shortname in short_installed:
Packit Service 11b429
            ops.append(makeop('config', name,
Packit Service 11b429
                              config=get_package_config(configs, name)))
Packit Service 11b429
Packit Service 11b429
    # prefer config entries to filepath entries
Packit Service 11b429
    for op in ops:
Packit Service 11b429
        if op['op'] != 'install' or not op['cfgfile']:
Packit Service 11b429
            continue
Packit Service 11b429
        name = op['name']
Packit Service 11b429
        fromcfg = get_package_config(configs, op['name'])
Packit Service 11b429
        if fromcfg:
Packit Service 11b429
            LOG.debug("preferring configs[%(name)s] over '%(cfgfile)s'", op)
Packit Service 11b429
            op['cfgfile'] = None
Packit Service 11b429
            op['config'] = fromcfg
Packit Service 11b429
Packit Service 11b429
    return ops
Packit Service 11b429
Packit Service 11b429
Packit Service 11b429
def render_snap_op(op, name, path=None, cfgfile=None, config=None):
Packit Service 11b429
    if op not in ('install', 'config'):
Packit Service 11b429
        raise ValueError("cannot render op '%s'" % op)
Packit Service 11b429
Packit Service 11b429
    shortname = name.partition(NAMESPACE_DELIM)[0]
Packit Service 11b429
    try:
Packit Service 11b429
        cfg_tmpf = None
Packit Service 11b429
        if config is not None:
Packit Service 11b429
            # input to 'snappy config packagename' must have nested data. odd.
Packit Service 11b429
            # config:
Packit Service 11b429
            #   packagename:
Packit Service 11b429
            #      config
Packit Service 11b429
            # Note, however, we do not touch config files on disk.
Packit Service 11b429
            nested_cfg = {'config': {shortname: config}}
Packit Service 11b429
            (fd, cfg_tmpf) = temp_utils.mkstemp()
Packit Service 11b429
            os.write(fd, safeyaml.dumps(nested_cfg).encode())
Packit Service 11b429
            os.close(fd)
Packit Service 11b429
            cfgfile = cfg_tmpf
Packit Service 11b429
Packit Service 11b429
        cmd = [SNAPPY_CMD, op]
Packit Service 11b429
        if op == 'install':
Packit Service 11b429
            if path:
Packit Service 11b429
                cmd.append("--allow-unauthenticated")
Packit Service 11b429
                cmd.append(path)
Packit Service 11b429
            else:
Packit Service 11b429
                cmd.append(name)
Packit Service 11b429
            if cfgfile:
Packit Service 11b429
                cmd.append(cfgfile)
Packit Service 11b429
        elif op == 'config':
Packit Service 11b429
            cmd += [name, cfgfile]
Packit Service 11b429
Packit Service 11b429
        util.subp(cmd)
Packit Service 11b429
Packit Service 11b429
    finally:
Packit Service 11b429
        if cfg_tmpf:
Packit Service 11b429
            os.unlink(cfg_tmpf)
Packit Service 11b429
Packit Service 11b429
Packit Service 11b429
def read_installed_packages():
Packit Service 11b429
    ret = []
Packit Service 11b429
    for (name, _date, _version, dev) in read_pkg_data():
Packit Service 11b429
        if dev:
Packit Service 11b429
            ret.append(NAMESPACE_DELIM.join([name, dev]))
Packit Service 11b429
        else:
Packit Service 11b429
            ret.append(name)
Packit Service 11b429
    return ret
Packit Service 11b429
Packit Service 11b429
Packit Service 11b429
def read_pkg_data():
Packit Service 11b429
    out, _err = util.subp([SNAPPY_CMD, "list"])
Packit Service 11b429
    pkg_data = []
Packit Service 11b429
    for line in out.splitlines()[1:]:
Packit Service 11b429
        toks = line.split(sep=None, maxsplit=3)
Packit Service 11b429
        if len(toks) == 3:
Packit Service 11b429
            (name, date, version) = toks
Packit Service 11b429
            dev = None
Packit Service 11b429
        else:
Packit Service 11b429
            (name, date, version, dev) = toks
Packit Service 11b429
        pkg_data.append((name, date, version, dev,))
Packit Service 11b429
    return pkg_data
Packit Service 11b429
Packit Service 11b429
Packit Service 11b429
def disable_enable_ssh(enabled):
Packit Service 11b429
    LOG.debug("setting enablement of ssh to: %s", enabled)
Packit Service 11b429
    # do something here that would enable or disable
Packit Service 11b429
    not_to_be_run = "/etc/ssh/sshd_not_to_be_run"
Packit Service 11b429
    if enabled:
Packit Service 11b429
        util.del_file(not_to_be_run)
Packit Service 11b429
        # this is an indempotent operation
Packit Service 11b429
        util.subp(["systemctl", "start", "ssh"])
Packit Service 11b429
    else:
Packit Service 11b429
        # this is an indempotent operation
Packit Service 11b429
        util.subp(["systemctl", "stop", "ssh"])
Packit Service 11b429
        util.write_file(not_to_be_run, "cloud-init\n")
Packit Service 11b429
Packit Service 11b429
Packit Service 11b429
def set_snappy_command():
Packit Service 11b429
    global SNAPPY_CMD
Packit Service 11b429
    if util.which("snappy-go"):
Packit Service 11b429
        SNAPPY_CMD = "snappy-go"
Packit Service 11b429
    elif util.which("snappy"):
Packit Service 11b429
        SNAPPY_CMD = "snappy"
Packit Service 11b429
    else:
Packit Service 11b429
        SNAPPY_CMD = "snap"
Packit Service 11b429
    LOG.debug("snappy command is '%s'", SNAPPY_CMD)
Packit Service 11b429
Packit Service 11b429
Packit Service 11b429
def handle(name, cfg, cloud, log, args):
Packit Service 11b429
    cfgin = cfg.get('snappy')
Packit Service 11b429
    if not cfgin:
Packit Service 11b429
        cfgin = {}
Packit Service 11b429
    mycfg = util.mergemanydict([cfgin, BUILTIN_CFG])
Packit Service 11b429
Packit Service 11b429
    sys_snappy = str(mycfg.get("system_snappy", "auto"))
Packit Service 11b429
    if util.is_false(sys_snappy):
Packit Service 11b429
        LOG.debug("%s: System is not snappy. disabling", name)
Packit Service 11b429
        return
Packit Service 11b429
Packit Service 11b429
    if sys_snappy.lower() == "auto" and not(util.system_is_snappy()):
Packit Service 11b429
        LOG.debug("%s: 'auto' mode, and system not snappy", name)
Packit Service 11b429
        return
Packit Service 11b429
Packit Service 11b429
    log.warning(
Packit Service 11b429
        'DEPRECATION: snappy module will be dropped in 18.3 release.'
Packit Service 11b429
        ' Use snap module instead')
Packit Service 11b429
Packit Service 11b429
    set_snappy_command()
Packit Service 11b429
Packit Service 11b429
    pkg_ops = get_package_ops(packages=mycfg['packages'],
Packit Service 11b429
                              configs=mycfg['config'],
Packit Service 11b429
                              fspath=mycfg['packages_dir'])
Packit Service 11b429
Packit Service 11b429
    fails = []
Packit Service 11b429
    for pkg_op in pkg_ops:
Packit Service 11b429
        try:
Packit Service 11b429
            render_snap_op(**pkg_op)
Packit Service 11b429
        except Exception as e:
Packit Service 11b429
            fails.append((pkg_op, e,))
Packit Service 11b429
            LOG.warning("'%s' failed for '%s': %s",
Packit Service 11b429
                        pkg_op['op'], pkg_op['name'], e)
Packit Service 11b429
Packit Service 11b429
    # Default to disabling SSH
Packit Service 11b429
    ssh_enabled = mycfg.get('ssh_enabled', "auto")
Packit Service 11b429
Packit Service 11b429
    # If the user has not explicitly enabled or disabled SSH, then enable it
Packit Service 11b429
    # when password SSH authentication is requested or there are SSH keys
Packit Service 11b429
    if ssh_enabled == "auto":
Packit Service 11b429
        user_ssh_keys = cloud.get_public_ssh_keys() or None
Packit Service 11b429
        password_auth_enabled = cfg.get('ssh_pwauth', False)
Packit Service 11b429
        if user_ssh_keys:
Packit Service 11b429
            LOG.debug("Enabling SSH, ssh keys found in datasource")
Packit Service 11b429
            ssh_enabled = True
Packit Service 11b429
        elif cfg.get('ssh_authorized_keys'):
Packit Service 11b429
            LOG.debug("Enabling SSH, ssh keys found in config")
Packit Service 11b429
        elif password_auth_enabled:
Packit Service 11b429
            LOG.debug("Enabling SSH, password authentication requested")
Packit Service 11b429
            ssh_enabled = True
Packit Service 11b429
    elif ssh_enabled not in (True, False):
Packit Service 11b429
        LOG.warning("Unknown value '%s' in ssh_enabled", ssh_enabled)
Packit Service 11b429
Packit Service 11b429
    disable_enable_ssh(ssh_enabled)
Packit Service 11b429
Packit Service 11b429
    if fails:
Packit Service 11b429
        raise Exception("failed to install/configure snaps")
Packit Service 11b429
Packit Service 11b429
# vi: ts=4 expandtab