summaryrefslogtreecommitdiff
path: root/cloudinit/config/cc_snap_config.py
blob: afe297eef3c86cbd7e4fd29f2c8450599b76de9d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
# Copyright (C) 2016 Canonical Ltd.
#
# Author: Ryan Harper <ryan.harper@canonical.com>
#
# This file is part of cloud-init. See LICENSE file for license information.

# RELEASE_BLOCKER: Remove this deprecated module in 18.3
"""
Snap Config
-----------
**Summary:** snap_config modules allows configuration of snapd.

**Deprecated**: Use :ref:`snap` module instead. This module will not exist
in cloud-init 18.3.

This module uses the same ``snappy`` namespace for configuration but
acts only only a subset of the configuration.

If ``assertions`` is set and the user has included a list of assertions
then cloud-init will collect the assertions into a single assertion file
and invoke ``snap ack <path to file with assertions>`` which will attempt
to load the provided assertions into the snapd assertion database.

If ``email`` is set, this value is used to create an authorized user for
contacting and installing snaps from the Ubuntu Store.  This is done by
calling ``snap create-user`` command.

If ``known`` is set to True, then it is expected the user also included
an assertion of type ``system-user``.  When ``snap create-user`` is called
cloud-init will append '--known' flag which instructs snapd to look for
a system-user assertion with the details.  If ``known`` is not set, then
``snap create-user`` will contact the Ubuntu SSO for validating and importing
a system-user for the instance.

.. note::
    If the system is already managed, then cloud-init will not attempt to
    create a system-user.

**Internal name:** ``cc_snap_config``

**Module frequency:** per instance

**Supported distros:** any with 'snapd' available

**Config keys**::

    #cloud-config
    snappy:
        assertions:
        - |
        <assertion 1>
        - |
        <assertion 2>
        email: user@user.org
        known: true

"""

from cloudinit import log as logging
from cloudinit.settings import PER_INSTANCE
from cloudinit import util

LOG = logging.getLogger(__name__)

frequency = PER_INSTANCE
SNAPPY_CMD = "snap"
ASSERTIONS_FILE = "/var/lib/cloud/instance/snapd.assertions"


"""
snappy:
  assertions:
  - |
  <snap assertion 1>
  - |
  <snap assertion 2>
  email: foo@foo.io
  known: true
"""


def add_assertions(assertions=None):
    """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:
        assertions = []

    if not isinstance(assertions, list):
        raise ValueError(
            'assertion parameter was not a list: {assertions}'.format(
                assertions=assertions))

    snap_cmd = [SNAPPY_CMD, 'ack']
    combined = "\n".join(assertions)
    if len(combined) == 0:
        raise ValueError("Assertion list is empty")

    for asrt in assertions:
        LOG.debug('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 add_snap_user(cfg=None):
    """Add a snap system-user if provided with email under snappy config.

      - Check that system is not already managed.
      - Check that if using a system-user assertion, that it's
        imported into snapd.

    Returns a dictionary to be passed to Distro.create_user
    """

    if not cfg:
        cfg = {}

    if not isinstance(cfg, dict):
        raise ValueError(
            'configuration parameter was not a dict: {cfg}'.format(cfg=cfg))

    snapuser = cfg.get('email', None)
    if not snapuser:
        return

    usercfg = {
        'snapuser': snapuser,
        'known': cfg.get('known', False),
    }

    # query if we're already registered
    out, _ = util.subp([SNAPPY_CMD, 'managed'], capture=True)
    if out.strip() == "true":
        LOG.warning('This device is already managed. '
                    'Skipping system-user creation')
        return

    if usercfg.get('known'):
        # Check that we imported a system-user assertion
        out, _ = util.subp([SNAPPY_CMD, 'known', 'system-user'],
                           capture=True)
        if len(out) == 0:
            LOG.error('Missing "system-user" assertion. '
                      'Check "snappy" user-data assertions.')
            return

    return usercfg


def handle(name, cfg, cloud, log, args):
    cfgin = cfg.get('snappy')
    if not cfgin:
        LOG.debug('No snappy config provided, skipping')
        return

    log.warning(
        'DEPRECATION: snap_config module will be dropped in 18.3 release.'
        ' Use snap module instead')
    if not(util.system_is_snappy()):
        LOG.debug("%s: system not snappy", name)
        return

    assertions = cfgin.get('assertions', [])
    if len(assertions) > 0:
        LOG.debug('Importing user-provided snap assertions')
        add_assertions(assertions)

    # Create a snap user if requested.
    # Snap systems contact the store with a user's email
    # and extract information needed to create a local user.
    # A user may provide a 'system-user' assertion which includes
    # the required information. Using such an assertion to create
    # a local user requires specifying 'known: true' in the supplied
    # user-data.
    usercfg = add_snap_user(cfg=cfgin)
    if usercfg:
        cloud.distro.create_user(usercfg.get('snapuser'), **usercfg)

# vi: ts=4 expandtab