Merge ~raharper/cloud-init:snapuser-create into cloud-init:master
- Git
- lp:~raharper/cloud-init
- snapuser-create
- Merge into master
Status: | Merged |
---|---|
Merged at revision: | 21632972df034c200578e1fbc121a07f20bb8774 |
Proposed branch: | ~raharper/cloud-init:snapuser-create |
Merge into: | cloud-init:master |
Diff against target: |
753 lines (+600/-15) 8 files modified
cloudinit/config/cc_snap_config.py (+183/-0) cloudinit/config/cc_snappy.py (+4/-14) cloudinit/distros/__init__.py (+35/-0) cloudinit/util.py (+12/-0) config/cloud.cfg (+1/-0) doc/examples/cloud-config-user-groups.txt (+8/-0) tests/unittests/test_distros/test_user_data_normalize.py (+65/-0) tests/unittests/test_handler/test_handler_snappy.py (+292/-1) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
cloud-init Commiters | Pending | ||
Review via email: mp+304700@code.launchpad.net |
Commit message
Description of the change
Add support for snap create-user on Ubuntu Core images
Ubuntu Core images use the `snap create-user` to add users to a Ubuntu
Core system. Add support for creating snap users by added a key to
the users dictionary:
users:
- name: bob
snapuser: <email address hidden>
Or via the 'snappy' dictionary:
snappy:
email: <email address hidden>
These methods will contact the Ubuntu SSO infrastructure to request
user information (including public ssh keys if present) for creating
the user.
Users may also create a snap user without contacting the SSO by
providing a 'system-user' assertion by importing them into snapd.
snappy:
email: <email address hidden>
known: true
assertions:
- |
<assertion text here>
Additionally, Ubuntu Core systems have a read-only /etc/passwd such that
the normal useradd/groupadd commands do not function without an additional
flag, '--extrausers', which redirects the pwd to /var/lib/
Move the system_is_snappy() check from cc_snappy module to util for
re-use and then update the Distro class to append '--extrausers' if
the system is Ubuntu Core.
- 90577ef... by Ryan Harper
-
Update snapuser example
Scott Moser (smoser) wrote : | # |
- ba627b5... by Ryan Harper
-
Merge remote-tracking branch 'origin/master' into snapuser-create
- e5a6164... by Ryan Harper
-
config/
cc_snap_ config: Add snap config module for early config of snapd During init, we want to import snap assertions which can be used to
create a snap 'system-user'. - 9fb5a34... by Ryan Harper
-
Fix cc_snap_config: run later and encode assertions
snapd requires cloud-init stage to complete, so we cannot
run snap_config during init stage, instead we can run
early during config stage.When writing out assertions, the default write mode is 'w+b'
and thus the input needs to be bytes[], so encode with utf-8. - a9b54d0... by Ryan Harper
-
Merge remote-tracking branch 'origin/master' into snapuser-create
- 76774db... by Ryan Harper
-
snappy: try harder to import system-user assertion
- b543c0f... by Ryan Harper
-
unittest: add test for when snappy system-user assertion is provided
- 3db49a7... by Ryan Harper
-
config/
cc_snap_ config: Add some config checking and unittests - Make sure we only run on system_is_snappy == True
- Handle some expected scenarios for various levels of config input.
- Handle the case where the device is already managed
- Add unittests to cover snap_config module.
Jon Grimm (jgrimm) wrote : | # |
Quick look just because I wanted to know more about the impl.
Ryan Harper (raharper) wrote : | # |
Thanks for looking at the code!
- a55ae1f... by Ryan Harper
-
debian: add explicit dependencies for required binaries
Jon Grimm (jgrimm) wrote : | # |
Thanks, it was enlightening and easy to read.
- 1b96428... by Ryan Harper
-
Merge branch 'fix-lp1619423' into snapuser-create
Scott Moser (smoser) wrote : | # |
over all, looks good.
you dont have to clean up the handle, but if you see easy way to do that that'd be nice.
is the snappy path now valid on non-snappy system ? (ubuntu server with 'snap' support).
lastly, please just review your commit message and make sure its up to date with all changes (it may well be, just think you made some changes since you wrote it).
Ryan Harper (raharper) wrote : | # |
On Wed, Oct 19, 2016 at 8:04 AM, Scott Moser <email address hidden> wrote:
> over all, looks good.
> you dont have to clean up the handle, but if you see easy way to do that
> that'd be nice.
>
> is the snappy path now valid on non-snappy system ? (ubuntu server with
> 'snap' support).
>
It is, and util.is_
confirm.
>
>
> lastly, please just review your commit message and make sure its up to
> date with all changes (it may well be, just think you made some changes
> since you wrote it).
>
ACK, it needs updating to account for the snappy: namespace changes.
>
>
> Diff comments:
>
> > diff --git a/cloudinit/
> b/cloudinit/
> > new file mode 100644
> > index 0000000..667b9c6
> > --- /dev/null
> > +++ b/cloudinit/
> > @@ -0,0 +1,177 @@
> > +# vi: ts=4 expandtab
> > +#
> > +# Copyright (C) 2016 Canonical Ltd.
> > +#
> > +# Author: Ryan Harper <email address hidden>
> > +#
> > +# This program is free software: you can redistribute it and/or
> modify
> > +# it under the terms of the GNU General Public License version 3, as
> > +# published by the Free Software Foundation.
> > +#
> > +# This program is distributed in the hope that it will be useful,
> > +# but WITHOUT ANY WARRANTY; without even the implied warranty of
> > +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
> > +# GNU General Public License for more details.
> > +#
> > +# You should have received a copy of the GNU General Public License
> > +# along with this program. If not, see <
> http://
> > +
> > +"""
> > +Snappy
> > +------
> > +**Summary:** snap_config modules allows configuration of snapd.
> > +
> > +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:** ubuntu
> > +
> > +**Config keys**::
> > +
> > + #cloud-confi...
Ryan Harper (raharper) wrote : | # |
On Wed, Oct 19, 2016 at 8:52 AM, Ryan Harper <email address hidden>
wrote:
>
>
> On Wed, Oct 19, 2016 at 8:04 AM, Scott Moser <email address hidden> wrote:
>
>> over all, looks good.
>> you dont have to clean up the handle, but if you see easy way to do that
>> that'd be nice.
>>
>> is the snappy path now valid on non-snappy system ? (ubuntu server with
>> 'snap' support).
>>
>
> It is, and util.is_
> confirm.
>
is_system_snappy is looking for 'all-snap' style images; so snaps on
classic don't
respond. We'd need to design something for validating that it works on
snaps on classic
setups.
One key difference is that all-snap systems have non-writable /etc ; where
as snapd on classic doesn't. If we added classic systems to the
is_system_snappy check, then users would get added
to /var/lib/extrausers instead of /etc; I think that's non-optimal and
introduces changes to existing
behavior.
Ryan
- 53d8cb4... by Ryan Harper
-
cc_snap_config: move snapuser creation out of handle and other cleanups
- Dropped distros = ['ubuntu']; snapd runs on other distros as well
- Dropped set_snappy_cmd; we require 'snap' for function
- Default assertions parameter to None to prevent object init
- Moved snapuser creation into method out of handle into add_snap_user
- Added unittests for new add_snap_user method
Scott Moser (smoser) wrote : | # |
over all, looks good.
you dont have to clean up the handle, but if you see easy way to do that that'd be nice.
is the snappy path now valid on non-snappy system ? (ubuntu server with 'snap' support).
lastly, please just review your commit message and make sure its up to date with all changes (it may well be, just think you made some changes since you wrote it).
- a353f18... by Ryan Harper
-
Revert "Merge branch 'fix-lp1619423' into snapuser-create"
This reverts commit 1b964281b506a7c
5ba156730fd22d6 9091be957f, reversing
changes made to 3db49a7a586cab7df957ca0fca979b 01431e49b3.
Scott Moser (smoser) wrote : | # |
I merged this.
Had to fix flake8 tox and made one code change. basically in add_snap_user i returned if there was no 'email' and then un-indented the rest of that block.
Preview Diff
1 | diff --git a/cloudinit/config/cc_snap_config.py b/cloudinit/config/cc_snap_config.py | |||
2 | 0 | new file mode 100644 | 0 | new file mode 100644 |
3 | index 0000000..ffb0768 | |||
4 | --- /dev/null | |||
5 | +++ b/cloudinit/config/cc_snap_config.py | |||
6 | @@ -0,0 +1,183 @@ | |||
7 | 1 | # vi: ts=4 expandtab | ||
8 | 2 | # | ||
9 | 3 | # Copyright (C) 2016 Canonical Ltd. | ||
10 | 4 | # | ||
11 | 5 | # Author: Ryan Harper <ryan.harper@canonical.com> | ||
12 | 6 | # | ||
13 | 7 | # This program is free software: you can redistribute it and/or modify | ||
14 | 8 | # it under the terms of the GNU General Public License version 3, as | ||
15 | 9 | # published by the Free Software Foundation. | ||
16 | 10 | # | ||
17 | 11 | # This program is distributed in the hope that it will be useful, | ||
18 | 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
19 | 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
20 | 14 | # GNU General Public License for more details. | ||
21 | 15 | # | ||
22 | 16 | # You should have received a copy of the GNU General Public License | ||
23 | 17 | # along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
24 | 18 | |||
25 | 19 | """ | ||
26 | 20 | Snappy | ||
27 | 21 | ------ | ||
28 | 22 | **Summary:** snap_config modules allows configuration of snapd. | ||
29 | 23 | |||
30 | 24 | This module uses the same ``snappy`` namespace for configuration but | ||
31 | 25 | acts only only a subset of the configuration. | ||
32 | 26 | |||
33 | 27 | If ``assertions`` is set and the user has included a list of assertions | ||
34 | 28 | then cloud-init will collect the assertions into a single assertion file | ||
35 | 29 | and invoke ``snap ack <path to file with assertions>`` which will attempt | ||
36 | 30 | to load the provided assertions into the snapd assertion database. | ||
37 | 31 | |||
38 | 32 | If ``email`` is set, this value is used to create an authorized user for | ||
39 | 33 | contacting and installing snaps from the Ubuntu Store. This is done by | ||
40 | 34 | calling ``snap create-user`` command. | ||
41 | 35 | |||
42 | 36 | If ``known`` is set to True, then it is expected the user also included | ||
43 | 37 | an assertion of type ``system-user``. When ``snap create-user`` is called | ||
44 | 38 | cloud-init will append '--known' flag which instructs snapd to look for | ||
45 | 39 | a system-user assertion with the details. If ``known`` is not set, then | ||
46 | 40 | ``snap create-user`` will contact the Ubuntu SSO for validating and importing | ||
47 | 41 | a system-user for the instance. | ||
48 | 42 | |||
49 | 43 | .. note:: | ||
50 | 44 | If the system is already managed, then cloud-init will not attempt to | ||
51 | 45 | create a system-user. | ||
52 | 46 | |||
53 | 47 | **Internal name:** ``cc_snap_config`` | ||
54 | 48 | |||
55 | 49 | **Module frequency:** per instance | ||
56 | 50 | |||
57 | 51 | **Supported distros:** any with 'snapd' available | ||
58 | 52 | |||
59 | 53 | **Config keys**:: | ||
60 | 54 | |||
61 | 55 | #cloud-config | ||
62 | 56 | snappy: | ||
63 | 57 | assertions: | ||
64 | 58 | - | | ||
65 | 59 | <assertion 1> | ||
66 | 60 | - | | ||
67 | 61 | <assertion 2> | ||
68 | 62 | email: user@user.org | ||
69 | 63 | known: true | ||
70 | 64 | |||
71 | 65 | """ | ||
72 | 66 | |||
73 | 67 | from cloudinit import log as logging | ||
74 | 68 | from cloudinit.settings import PER_INSTANCE | ||
75 | 69 | from cloudinit import util | ||
76 | 70 | |||
77 | 71 | LOG = logging.getLogger(__name__) | ||
78 | 72 | |||
79 | 73 | frequency = PER_INSTANCE | ||
80 | 74 | SNAPPY_CMD = "snap" | ||
81 | 75 | ASSERTIONS_FILE = "/var/lib/cloud/instance/snapd.assertions" | ||
82 | 76 | |||
83 | 77 | |||
84 | 78 | """ | ||
85 | 79 | snappy: | ||
86 | 80 | assertions: | ||
87 | 81 | - | | ||
88 | 82 | <snap assertion 1> | ||
89 | 83 | - | | ||
90 | 84 | <snap assertion 2> | ||
91 | 85 | email: foo@foo.io | ||
92 | 86 | known: true | ||
93 | 87 | """ | ||
94 | 88 | |||
95 | 89 | |||
96 | 90 | def add_assertions(assertions=None): | ||
97 | 91 | """ Import list of assertions by concatenating each | ||
98 | 92 | assertion into a string separated by a '\n'. | ||
99 | 93 | Write this string to a instance file and | ||
100 | 94 | then invoke `snap ack /path/to/file` | ||
101 | 95 | and check for errors. | ||
102 | 96 | |||
103 | 97 | If snap exits 0, then all assertions are imported. | ||
104 | 98 | """ | ||
105 | 99 | if not assertions: | ||
106 | 100 | assertions = [] | ||
107 | 101 | |||
108 | 102 | if not isinstance(assertions, list): | ||
109 | 103 | raise ValueError('assertion parameter was not a list: %s', assertions) | ||
110 | 104 | |||
111 | 105 | snap_cmd = [SNAPPY_CMD, 'ack'] | ||
112 | 106 | combined = "\n".join(assertions) | ||
113 | 107 | if len(combined) == 0: | ||
114 | 108 | raise ValueError("Assertion list is empty") | ||
115 | 109 | |||
116 | 110 | for asrt in assertions: | ||
117 | 111 | LOG.debug('Acking: %s', asrt.split('\n')[0:2]) | ||
118 | 112 | |||
119 | 113 | util.write_file(ASSERTIONS_FILE, combined.encode('utf-8')) | ||
120 | 114 | util.subp(snap_cmd + [ASSERTIONS_FILE], capture=True) | ||
121 | 115 | |||
122 | 116 | |||
123 | 117 | def add_snap_user(cfg=None): | ||
124 | 118 | """ Add a snap system-user if provided with email value | ||
125 | 119 | under snappy config. | ||
126 | 120 | - Check that system is not already managed. | ||
127 | 121 | - Check that if using a system-user assertion, that it's | ||
128 | 122 | imported into snapd. | ||
129 | 123 | |||
130 | 124 | Returns a dictionary to be passed to Distro.create_user | ||
131 | 125 | """ | ||
132 | 126 | |||
133 | 127 | if not cfg: | ||
134 | 128 | cfg = {} | ||
135 | 129 | |||
136 | 130 | if not isinstance(cfg, dict): | ||
137 | 131 | raise ValueError('configuration parameter was not a dict: %s', cfg) | ||
138 | 132 | |||
139 | 133 | snapuser = cfg.get('email', None) | ||
140 | 134 | if snapuser: | ||
141 | 135 | usercfg = { | ||
142 | 136 | 'snapuser': snapuser, | ||
143 | 137 | 'known': cfg.get('known', False), | ||
144 | 138 | } | ||
145 | 139 | |||
146 | 140 | # query if we're already registered | ||
147 | 141 | out, _ = util.subp([SNAPPY_CMD, 'managed'], capture=True) | ||
148 | 142 | if out.strip() == "true": | ||
149 | 143 | LOG.warning('This device is already managed. ' | ||
150 | 144 | 'Skipping system-user creation') | ||
151 | 145 | return | ||
152 | 146 | |||
153 | 147 | if usercfg.get('known'): | ||
154 | 148 | # Check that we imported a system-user assertion | ||
155 | 149 | out, _ = util.subp([SNAPPY_CMD, 'known', 'system-user'], | ||
156 | 150 | capture=True) | ||
157 | 151 | if len(out) == 0: | ||
158 | 152 | LOG.error('Missing "system-user" assertion. ' | ||
159 | 153 | 'Check "snappy" user-data assertions.') | ||
160 | 154 | return | ||
161 | 155 | |||
162 | 156 | return usercfg | ||
163 | 157 | |||
164 | 158 | |||
165 | 159 | def handle(name, cfg, cloud, log, args): | ||
166 | 160 | cfgin = cfg.get('snappy') | ||
167 | 161 | if not cfgin: | ||
168 | 162 | LOG.debug('No snappy config provided, skipping') | ||
169 | 163 | return | ||
170 | 164 | |||
171 | 165 | if not(util.system_is_snappy()): | ||
172 | 166 | LOG.debug("%s: system not snappy", name) | ||
173 | 167 | return | ||
174 | 168 | |||
175 | 169 | assertions = cfgin.get('assertions', []) | ||
176 | 170 | if len(assertions) > 0: | ||
177 | 171 | LOG.debug('Importing user-provided snap assertions') | ||
178 | 172 | add_assertions(assertions) | ||
179 | 173 | |||
180 | 174 | # Create a snap user if requested. | ||
181 | 175 | # Snap systems contact the store with a user's email | ||
182 | 176 | # and extract information needed to create a local user. | ||
183 | 177 | # A user may provide a 'system-user' assertion which includes | ||
184 | 178 | # the required information. Using such an assertion to create | ||
185 | 179 | # a local user requires specifying 'known: true' in the supplied | ||
186 | 180 | # user-data. | ||
187 | 181 | usercfg = add_snap_user(cfg=cfgin) | ||
188 | 182 | if usercfg: | ||
189 | 183 | cloud.distro.create_user(usercfg.get('snapuser'), **usercfg) | ||
190 | diff --git a/cloudinit/config/cc_snappy.py b/cloudinit/config/cc_snappy.py | |||
191 | index 36db9e6..e03ec48 100644 | |||
192 | --- a/cloudinit/config/cc_snappy.py | |||
193 | +++ b/cloudinit/config/cc_snappy.py | |||
194 | @@ -257,24 +257,14 @@ def disable_enable_ssh(enabled): | |||
195 | 257 | util.write_file(not_to_be_run, "cloud-init\n") | 257 | util.write_file(not_to_be_run, "cloud-init\n") |
196 | 258 | 258 | ||
197 | 259 | 259 | ||
198 | 260 | def system_is_snappy(): | ||
199 | 261 | # channel.ini is configparser loadable. | ||
200 | 262 | # snappy will move to using /etc/system-image/config.d/*.ini | ||
201 | 263 | # this is certainly not a perfect test, but good enough for now. | ||
202 | 264 | content = util.load_file("/etc/system-image/channel.ini", quiet=True) | ||
203 | 265 | if 'ubuntu-core' in content.lower(): | ||
204 | 266 | return True | ||
205 | 267 | if os.path.isdir("/etc/system-image/config.d/"): | ||
206 | 268 | return True | ||
207 | 269 | return False | ||
208 | 270 | |||
209 | 271 | |||
210 | 272 | def set_snappy_command(): | 260 | def set_snappy_command(): |
211 | 273 | global SNAPPY_CMD | 261 | global SNAPPY_CMD |
212 | 274 | if util.which("snappy-go"): | 262 | if util.which("snappy-go"): |
213 | 275 | SNAPPY_CMD = "snappy-go" | 263 | SNAPPY_CMD = "snappy-go" |
215 | 276 | else: | 264 | elif util.which("snappy"): |
216 | 277 | SNAPPY_CMD = "snappy" | 265 | SNAPPY_CMD = "snappy" |
217 | 266 | else: | ||
218 | 267 | SNAPPY_CMD = "snap" | ||
219 | 278 | LOG.debug("snappy command is '%s'", SNAPPY_CMD) | 268 | LOG.debug("snappy command is '%s'", SNAPPY_CMD) |
220 | 279 | 269 | ||
221 | 280 | 270 | ||
222 | @@ -289,7 +279,7 @@ def handle(name, cfg, cloud, log, args): | |||
223 | 289 | LOG.debug("%s: System is not snappy. disabling", name) | 279 | LOG.debug("%s: System is not snappy. disabling", name) |
224 | 290 | return | 280 | return |
225 | 291 | 281 | ||
227 | 292 | if sys_snappy.lower() == "auto" and not(system_is_snappy()): | 282 | if sys_snappy.lower() == "auto" and not(util.system_is_snappy()): |
228 | 293 | LOG.debug("%s: 'auto' mode, and system not snappy", name) | 283 | LOG.debug("%s: 'auto' mode, and system not snappy", name) |
229 | 294 | return | 284 | return |
230 | 295 | 285 | ||
231 | diff --git a/cloudinit/distros/__init__.py b/cloudinit/distros/__init__.py | |||
232 | index 78adf5f..4a72643 100755 | |||
233 | --- a/cloudinit/distros/__init__.py | |||
234 | +++ b/cloudinit/distros/__init__.py | |||
235 | @@ -367,6 +367,9 @@ class Distro(object): | |||
236 | 367 | 367 | ||
237 | 368 | adduser_cmd = ['useradd', name] | 368 | adduser_cmd = ['useradd', name] |
238 | 369 | log_adduser_cmd = ['useradd', name] | 369 | log_adduser_cmd = ['useradd', name] |
239 | 370 | if util.system_is_snappy(): | ||
240 | 371 | adduser_cmd.append('--extrausers') | ||
241 | 372 | log_adduser_cmd.append('--extrausers') | ||
242 | 370 | 373 | ||
243 | 371 | # Since we are creating users, we want to carefully validate the | 374 | # Since we are creating users, we want to carefully validate the |
244 | 372 | # inputs. If something goes wrong, we can end up with a system | 375 | # inputs. If something goes wrong, we can end up with a system |
245 | @@ -445,6 +448,32 @@ class Distro(object): | |||
246 | 445 | util.logexc(LOG, "Failed to create user %s", name) | 448 | util.logexc(LOG, "Failed to create user %s", name) |
247 | 446 | raise e | 449 | raise e |
248 | 447 | 450 | ||
249 | 451 | def add_snap_user(self, name, **kwargs): | ||
250 | 452 | """ | ||
251 | 453 | Add a snappy user to the system using snappy tools | ||
252 | 454 | """ | ||
253 | 455 | |||
254 | 456 | snapuser = kwargs.get('snapuser') | ||
255 | 457 | known = kwargs.get('known', False) | ||
256 | 458 | adduser_cmd = ["snap", "create-user", "--sudoer", "--json"] | ||
257 | 459 | if known: | ||
258 | 460 | adduser_cmd.append("--known") | ||
259 | 461 | adduser_cmd.append(snapuser) | ||
260 | 462 | |||
261 | 463 | # Run the command | ||
262 | 464 | LOG.debug("Adding snap user %s", name) | ||
263 | 465 | try: | ||
264 | 466 | (out, err) = util.subp(adduser_cmd, logstring=adduser_cmd, | ||
265 | 467 | capture=True) | ||
266 | 468 | LOG.debug("snap create-user returned: %s:%s", out, err) | ||
267 | 469 | jobj = util.load_json(out) | ||
268 | 470 | username = jobj.get('username', None) | ||
269 | 471 | except Exception as e: | ||
270 | 472 | util.logexc(LOG, "Failed to create snap user %s", name) | ||
271 | 473 | raise e | ||
272 | 474 | |||
273 | 475 | return username | ||
274 | 476 | |||
275 | 448 | def create_user(self, name, **kwargs): | 477 | def create_user(self, name, **kwargs): |
276 | 449 | """ | 478 | """ |
277 | 450 | Creates users for the system using the GNU passwd tools. This | 479 | Creates users for the system using the GNU passwd tools. This |
278 | @@ -452,6 +481,10 @@ class Distro(object): | |||
279 | 452 | distros where useradd is not desirable or not available. | 481 | distros where useradd is not desirable or not available. |
280 | 453 | """ | 482 | """ |
281 | 454 | 483 | ||
282 | 484 | # Add a snap user, if requested | ||
283 | 485 | if 'snapuser' in kwargs: | ||
284 | 486 | return self.add_snap_user(name, **kwargs) | ||
285 | 487 | |||
286 | 455 | # Add the user | 488 | # Add the user |
287 | 456 | self.add_user(name, **kwargs) | 489 | self.add_user(name, **kwargs) |
288 | 457 | 490 | ||
289 | @@ -602,6 +635,8 @@ class Distro(object): | |||
290 | 602 | 635 | ||
291 | 603 | def create_group(self, name, members=None): | 636 | def create_group(self, name, members=None): |
292 | 604 | group_add_cmd = ['groupadd', name] | 637 | group_add_cmd = ['groupadd', name] |
293 | 638 | if util.system_is_snappy(): | ||
294 | 639 | group_add_cmd.append('--extrausers') | ||
295 | 605 | if not members: | 640 | if not members: |
296 | 606 | members = [] | 641 | members = [] |
297 | 607 | 642 | ||
298 | diff --git a/cloudinit/util.py b/cloudinit/util.py | |||
299 | index eb3e589..3a698df 100644 | |||
300 | --- a/cloudinit/util.py | |||
301 | +++ b/cloudinit/util.py | |||
302 | @@ -2377,3 +2377,15 @@ def get_installed_packages(target=None): | |||
303 | 2377 | pkgs_inst.add(re.sub(":.*", "", pkg)) | 2377 | pkgs_inst.add(re.sub(":.*", "", pkg)) |
304 | 2378 | 2378 | ||
305 | 2379 | return pkgs_inst | 2379 | return pkgs_inst |
306 | 2380 | |||
307 | 2381 | |||
308 | 2382 | def system_is_snappy(): | ||
309 | 2383 | # channel.ini is configparser loadable. | ||
310 | 2384 | # snappy will move to using /etc/system-image/config.d/*.ini | ||
311 | 2385 | # this is certainly not a perfect test, but good enough for now. | ||
312 | 2386 | content = load_file("/etc/system-image/channel.ini", quiet=True) | ||
313 | 2387 | if 'ubuntu-core' in content.lower(): | ||
314 | 2388 | return True | ||
315 | 2389 | if os.path.isdir("/etc/system-image/config.d/"): | ||
316 | 2390 | return True | ||
317 | 2391 | return False | ||
318 | diff --git a/config/cloud.cfg b/config/cloud.cfg | |||
319 | index d608dc8..1b93e7f 100644 | |||
320 | --- a/config/cloud.cfg | |||
321 | +++ b/config/cloud.cfg | |||
322 | @@ -45,6 +45,7 @@ cloud_config_modules: | |||
323 | 45 | # Emit the cloud config ready event | 45 | # Emit the cloud config ready event |
324 | 46 | # this can be used by upstart jobs for 'start on cloud-config'. | 46 | # this can be used by upstart jobs for 'start on cloud-config'. |
325 | 47 | - emit_upstart | 47 | - emit_upstart |
326 | 48 | - snap_config | ||
327 | 48 | - ssh-import-id | 49 | - ssh-import-id |
328 | 49 | - locale | 50 | - locale |
329 | 50 | - set-passwords | 51 | - set-passwords |
330 | diff --git a/doc/examples/cloud-config-user-groups.txt b/doc/examples/cloud-config-user-groups.txt | |||
331 | index 0e8ed24..d0082f3 100644 | |||
332 | --- a/doc/examples/cloud-config-user-groups.txt | |||
333 | +++ b/doc/examples/cloud-config-user-groups.txt | |||
334 | @@ -30,6 +30,7 @@ users: | |||
335 | 30 | gecos: Magic Cloud App Daemon User | 30 | gecos: Magic Cloud App Daemon User |
336 | 31 | inactive: true | 31 | inactive: true |
337 | 32 | system: true | 32 | system: true |
338 | 33 | - snapuser: joe@joeuser.io | ||
339 | 33 | 34 | ||
340 | 34 | # Valid Values: | 35 | # Valid Values: |
341 | 35 | # name: The user's login name | 36 | # name: The user's login name |
342 | @@ -80,6 +81,13 @@ users: | |||
343 | 80 | # cloud-init does not parse/check the syntax of the sudo | 81 | # cloud-init does not parse/check the syntax of the sudo |
344 | 81 | # directive. | 82 | # directive. |
345 | 82 | # system: Create the user as a system user. This means no home directory. | 83 | # system: Create the user as a system user. This means no home directory. |
346 | 84 | # snapuser: Create a Snappy (Ubuntu-Core) user via the snap create-user | ||
347 | 85 | # command available on Ubuntu systems. If the user has an account | ||
348 | 86 | # on the Ubuntu SSO, specifying the email will allow snap to | ||
349 | 87 | # request a username and any public ssh keys and will import | ||
350 | 88 | # these into the system with username specifed by SSO account. | ||
351 | 89 | # If 'username' is not set in SSO, then username will be the | ||
352 | 90 | # shortname before the email domain. | ||
353 | 83 | # | 91 | # |
354 | 84 | 92 | ||
355 | 85 | # Default user creation: | 93 | # Default user creation: |
356 | diff --git a/tests/unittests/test_distros/test_user_data_normalize.py b/tests/unittests/test_distros/test_user_data_normalize.py | |||
357 | index b24888f..33bf922 100755 | |||
358 | --- a/tests/unittests/test_distros/test_user_data_normalize.py | |||
359 | +++ b/tests/unittests/test_distros/test_user_data_normalize.py | |||
360 | @@ -4,6 +4,7 @@ from cloudinit import helpers | |||
361 | 4 | from cloudinit import settings | 4 | from cloudinit import settings |
362 | 5 | 5 | ||
363 | 6 | from ..helpers import TestCase | 6 | from ..helpers import TestCase |
364 | 7 | import mock | ||
365 | 7 | 8 | ||
366 | 8 | 9 | ||
367 | 9 | bcfg = { | 10 | bcfg = { |
368 | @@ -296,3 +297,67 @@ class TestUGNormalize(TestCase): | |||
369 | 296 | self.assertIn('bob', users) | 297 | self.assertIn('bob', users) |
370 | 297 | self.assertEqual({'default': False}, users['joe']) | 298 | self.assertEqual({'default': False}, users['joe']) |
371 | 298 | self.assertEqual({'default': False}, users['bob']) | 299 | self.assertEqual({'default': False}, users['bob']) |
372 | 300 | |||
373 | 301 | @mock.patch('cloudinit.util.subp') | ||
374 | 302 | def test_create_snap_user(self, mock_subp): | ||
375 | 303 | mock_subp.side_effect = [('{"username": "joe", "ssh-key-count": 1}\n', | ||
376 | 304 | '')] | ||
377 | 305 | distro = self._make_distro('ubuntu') | ||
378 | 306 | ug_cfg = { | ||
379 | 307 | 'users': [ | ||
380 | 308 | {'name': 'joe', 'snapuser': 'joe@joe.com'}, | ||
381 | 309 | ], | ||
382 | 310 | } | ||
383 | 311 | (users, _groups) = self._norm(ug_cfg, distro) | ||
384 | 312 | for (user, config) in users.items(): | ||
385 | 313 | print('user=%s config=%s' % (user, config)) | ||
386 | 314 | username = distro.create_user(user, **config) | ||
387 | 315 | |||
388 | 316 | snapcmd = ['snap', 'create-user', '--sudoer', '--json', 'joe@joe.com'] | ||
389 | 317 | mock_subp.assert_called_with(snapcmd, capture=True, logstring=snapcmd) | ||
390 | 318 | self.assertEqual(username, 'joe') | ||
391 | 319 | |||
392 | 320 | @mock.patch('cloudinit.util.subp') | ||
393 | 321 | def test_create_snap_user_known(self, mock_subp): | ||
394 | 322 | mock_subp.side_effect = [('{"username": "joe", "ssh-key-count": 1}\n', | ||
395 | 323 | '')] | ||
396 | 324 | distro = self._make_distro('ubuntu') | ||
397 | 325 | ug_cfg = { | ||
398 | 326 | 'users': [ | ||
399 | 327 | {'name': 'joe', 'snapuser': 'joe@joe.com', 'known': True}, | ||
400 | 328 | ], | ||
401 | 329 | } | ||
402 | 330 | (users, _groups) = self._norm(ug_cfg, distro) | ||
403 | 331 | for (user, config) in users.items(): | ||
404 | 332 | print('user=%s config=%s' % (user, config)) | ||
405 | 333 | username = distro.create_user(user, **config) | ||
406 | 334 | |||
407 | 335 | snapcmd = ['snap', 'create-user', '--sudoer', '--json', '--known', | ||
408 | 336 | 'joe@joe.com'] | ||
409 | 337 | mock_subp.assert_called_with(snapcmd, capture=True, logstring=snapcmd) | ||
410 | 338 | self.assertEqual(username, 'joe') | ||
411 | 339 | |||
412 | 340 | @mock.patch('cloudinit.util.system_is_snappy') | ||
413 | 341 | @mock.patch('cloudinit.util.is_group') | ||
414 | 342 | @mock.patch('cloudinit.util.subp') | ||
415 | 343 | def test_add_user_on_snappy_system(self, mock_subp, mock_isgrp, | ||
416 | 344 | mock_snappy): | ||
417 | 345 | mock_isgrp.return_value = False | ||
418 | 346 | mock_subp.return_value = True | ||
419 | 347 | mock_snappy.return_value = True | ||
420 | 348 | distro = self._make_distro('ubuntu') | ||
421 | 349 | ug_cfg = { | ||
422 | 350 | 'users': [ | ||
423 | 351 | {'name': 'joe', 'groups': 'users', 'create_groups': True}, | ||
424 | 352 | ], | ||
425 | 353 | } | ||
426 | 354 | (users, _groups) = self._norm(ug_cfg, distro) | ||
427 | 355 | for (user, config) in users.items(): | ||
428 | 356 | print('user=%s config=%s' % (user, config)) | ||
429 | 357 | distro.add_user(user, **config) | ||
430 | 358 | |||
431 | 359 | groupcmd = ['groupadd', 'users', '--extrausers'] | ||
432 | 360 | addcmd = ['useradd', 'joe', '--extrausers', '--groups', 'users', '-m'] | ||
433 | 361 | |||
434 | 362 | mock_subp.assert_any_call(groupcmd) | ||
435 | 363 | mock_subp.assert_any_call(addcmd, logstring=addcmd) | ||
436 | diff --git a/tests/unittests/test_handler/test_handler_snappy.py b/tests/unittests/test_handler/test_handler_snappy.py | |||
437 | index 57dce1b..e320dd8 100644 | |||
438 | --- a/tests/unittests/test_handler/test_handler_snappy.py | |||
439 | +++ b/tests/unittests/test_handler/test_handler_snappy.py | |||
440 | @@ -1,14 +1,22 @@ | |||
441 | 1 | from cloudinit.config.cc_snappy import ( | 1 | from cloudinit.config.cc_snappy import ( |
442 | 2 | makeop, get_package_ops, render_snap_op) | 2 | makeop, get_package_ops, render_snap_op) |
444 | 3 | from cloudinit import util | 3 | from cloudinit.config.cc_snap_config import ( |
445 | 4 | add_assertions, add_snap_user, ASSERTIONS_FILE) | ||
446 | 5 | from cloudinit import (distros, helpers, cloud, util) | ||
447 | 6 | from cloudinit.config.cc_snap_config import handle as snap_handle | ||
448 | 7 | from cloudinit.sources import DataSourceNone | ||
449 | 8 | from ..helpers import FilesystemMockingTestCase, mock | ||
450 | 4 | 9 | ||
451 | 5 | from .. import helpers as t_help | 10 | from .. import helpers as t_help |
452 | 6 | 11 | ||
453 | 12 | import logging | ||
454 | 7 | import os | 13 | import os |
455 | 8 | import shutil | 14 | import shutil |
456 | 9 | import tempfile | 15 | import tempfile |
457 | 16 | import textwrap | ||
458 | 10 | import yaml | 17 | import yaml |
459 | 11 | 18 | ||
460 | 19 | LOG = logging.getLogger(__name__) | ||
461 | 12 | ALLOWED = (dict, list, int, str) | 20 | ALLOWED = (dict, list, int, str) |
462 | 13 | 21 | ||
463 | 14 | 22 | ||
464 | @@ -287,6 +295,289 @@ class TestInstallPackages(t_help.TestCase): | |||
465 | 287 | self.assertEqual(yaml.safe_load(mydata), data_found) | 295 | self.assertEqual(yaml.safe_load(mydata), data_found) |
466 | 288 | 296 | ||
467 | 289 | 297 | ||
468 | 298 | class TestSnapConfig(FilesystemMockingTestCase): | ||
469 | 299 | |||
470 | 300 | SYSTEM_USER_ASSERTION = textwrap.dedent(""" | ||
471 | 301 | type: system-user | ||
472 | 302 | authority-id: LqvZQdfyfGlYvtep4W6Oj6pFXP9t1Ksp | ||
473 | 303 | brand-id: LqvZQdfyfGlYvtep4W6Oj6pFXP9t1Ksp | ||
474 | 304 | email: foo@bar.com | ||
475 | 305 | password: $6$E5YiAuMIPAwX58jG$miomhVNui/vf7f/3ctB/f0RWSKFxG0YXzrJ9rtJ1ikvzt | ||
476 | 306 | series: | ||
477 | 307 | - 16 | ||
478 | 308 | since: 2016-09-10T16:34:00+03:00 | ||
479 | 309 | until: 2017-11-10T16:34:00+03:00 | ||
480 | 310 | username: baz | ||
481 | 311 | sign-key-sha3-384: RuVvnp4n52GilycjfbbTCI3_L8Y6QlIE75wxMc0KzGV3AUQqVd9GuXoj | ||
482 | 312 | |||
483 | 313 | AcLBXAQAAQoABgUCV/UU1wAKCRBKnlMoJQLkZVeLD/9/+hIeVywtzsDA3oxl+P+u9D13y9s6svP | ||
484 | 314 | Jd6Wnf4FTw6sq1GjBE4ZA7lrwSaRCUJ9Vcsvf2q9OGPY7mOb2TBxaDe0PbUMjrSrqllSSQwhpNI | ||
485 | 315 | zG+NxkkKuxsUmLzFa+k9m6cyojNbw5LFhQZBQCGlr3JYqC0tIREq/UsZxj+90TUC87lDJwkU8GF | ||
486 | 316 | s4CR+rejZj4itIcDcVxCSnJH6hv6j2JrJskJmvObqTnoOlcab+JXdamXqbldSP3UIhWoyVjqzkj | ||
487 | 317 | +to7mXgx+cCUA9+ngNCcfUG+1huGGTWXPCYkZ78HvErcRlIdeo4d3xwtz1cl/w3vYnq9og1XwsP | ||
488 | 318 | Yfetr3boig2qs1Y+j/LpsfYBYncgWjeDfAB9ZZaqQz/oc8n87tIPZDJHrusTlBfop8CqcM4xsKS | ||
489 | 319 | d+wnEY8e/F24mdSOYmS1vQCIDiRU3MKb6x138Ud6oHXFlRBbBJqMMctPqWDunWzb5QJ7YR0I39q | ||
490 | 320 | BrnEqv5NE0G7w6HOJ1LSPG5Hae3P4T2ea+ATgkb03RPr3KnXnzXg4TtBbW1nytdlgoNc/BafE1H | ||
491 | 321 | f3NThcq9gwX4xWZ2PAWnqVPYdDMyCtzW3Ck+o6sIzx+dh4gDLPHIi/6TPe/pUuMop9CBpWwez7V | ||
492 | 322 | v1z+1+URx6Xlq3Jq18y5pZ6fY3IDJ6km2nQPMzcm4Q==""") | ||
493 | 323 | |||
494 | 324 | ACCOUNT_ASSERTION = textwrap.dedent(""" | ||
495 | 325 | type: account-key | ||
496 | 326 | authority-id: canonical | ||
497 | 327 | revision: 2 | ||
498 | 328 | public-key-sha3-384: BWDEoaqyr25nF5SNCvEv2v7QnM9QsfCc0PBMYD_i2NGSQ32EF2d4D0 | ||
499 | 329 | account-id: canonical | ||
500 | 330 | name: store | ||
501 | 331 | since: 2016-04-01T00:00:00.0Z | ||
502 | 332 | body-length: 717 | ||
503 | 333 | sign-key-sha3-384: -CvQKAwRQ5h3Ffn10FILJoEZUXOv6km9FwA80-Rcj-f-6jadQ89VRswH | ||
504 | 334 | |||
505 | 335 | AcbBTQRWhcGAARAA0KKYYQWuHOrsFVi4p4l7ZzSvX7kLgJFFeFgOkzdWKBTHEnsMKjl5mefFe9j | ||
506 | 336 | qe8NlmJdfY7BenP7XeBtwKp700H/t9lLrZbpTNAPHXYxEWFJp5bPqIcJYBZ+29oLVLN1Tc5X482 | ||
507 | 337 | vCiDqL8+pPYqBrK2fNlyPlNNSum9wI70rDDL4r6FVvr+osTnGejibdV8JphWX+lrSQDnRSdM8KJ | ||
508 | 338 | UM43vTgLGTi9W54oRhsA2OFexRfRksTrnqGoonCjqX5wO3OFSaMDzMsO2MJ/hPfLgDqw53qjzuK | ||
509 | 339 | Iec9OL3k5basvu2cj5u9tKwVFDsCKK2GbKUsWWpx2KTpOifmhmiAbzkTHbH9KaoMS7p0kJwhTQG | ||
510 | 340 | o9aJ9VMTWHJc/NCBx7eu451u6d46sBPCXS/OMUh2766fQmoRtO1OwCTxsRKG2kkjbMn54UdFULl | ||
511 | 341 | VfzvyghMNRKIezsEkmM8wueTqGUGZWa6CEZqZKwhe/PROxOPYzqtDH18XZknbU1n5lNb7vNfem9 | ||
512 | 342 | 2ai+3+JyFnW9UhfvpVF7gzAgdyCqNli4C6BIN43uwoS8HkykocZS/+Gv52aUQ/NZ8BKOHLw+7an | ||
513 | 343 | Q0o8W9ltSLZbEMxFIPSN0stiZlkXAp6DLyvh1Y4wXSynDjUondTpej2fSvSlCz/W5v5V7qA4nIc | ||
514 | 344 | vUvV7RjVzv17ut0AEQEAAQ== | ||
515 | 345 | |||
516 | 346 | AcLDXAQAAQoABgUCV83k9QAKCRDUpVvql9g3IBT8IACKZ7XpiBZ3W4lqbPssY6On81WmxQLtvsM | ||
517 | 347 | WTp6zZpl/wWOSt2vMNUk9pvcmrNq1jG9CuhDfWFLGXEjcrrmVkN3YuCOajMSPFCGrxsIBLSRt/b | ||
518 | 348 | nrKykdLAAzMfG8rP1d82bjFFiIieE+urQ0Kcv09Jtdvavq3JT1Tek5mFyyfhHNlQEKOzWqmRWiL | ||
519 | 349 | 3c3VOZUs1ZD8TSlnuq/x+5T0X0YtOyGjSlVxk7UybbyMNd6MZfNaMpIG4x+mxD3KHFtBAC7O6kL | ||
520 | 350 | eX3i6j5nCY5UABfA3DZEAkWP4zlmdBEOvZ9t293NaDdOpzsUHRkoi0Zez/9BHQ/kwx/uNc2WqrY | ||
521 | 351 | inCmu16JGNeXqsyinnLl7Ghn2RwhvDMlLxF6RTx8xdx1yk6p3PBTwhZMUvuZGjUtN/AG8BmVJQ1 | ||
522 | 352 | rsGSRkkSywvnhVJRB2sudnrMBmNS2goJbzSbmJnOlBrd2WsV0T9SgNMWZBiov3LvU4o2SmAb6b+ | ||
523 | 353 | rYwh8H5QHcuuYJuxDjFhPswIp6Wes5T6hUicf3SWtObcDS4HSkVS4ImBjjX9YgCuFy7QdnooOWE | ||
524 | 354 | aPvkRw3XCVeYq0K6w9GRsk1YFErD4XmXXZjDYY650MX9v42Sz5MmphHV8jdIY5ssbadwFSe2rCQ | ||
525 | 355 | 6UX08zy7RsIb19hTndE6ncvSNDChUR9eEnCm73eYaWTWTnq1cxdVP/s52r8uss++OYOkPWqh5nO | ||
526 | 356 | haRn7INjH/yZX4qXjNXlTjo0PnHH0q08vNKDwLhxS+D9du+70FeacXFyLIbcWllSbJ7DmbumGpF | ||
527 | 357 | yYbtj3FDDPzachFQdIG3lSt+cSUGeyfSs6wVtc3cIPka/2Urx7RprfmoWSI6+a5NcLdj0u2z8O9 | ||
528 | 358 | HxeIgxDpg/3gT8ZIuFKePMcLDM19Fh/p0ysCsX+84B9chNWtsMSmIaE57V+959MVtsLu7SLb9gi | ||
529 | 359 | skrju0pQCwsu2wHMLTNd1f3PTHmrr49hxetTus07HSQUApMtAGKzQilF5zqFjbyaTd4xgQbd+PK | ||
530 | 360 | CjFyzQTDOcUhXpuUGt/IzlqiFfsCsmbj2K4KdSNYMlqIgZ3Azu8KvZLIhsyN7v5vNIZSPfEbjde | ||
531 | 361 | ClU9r0VRiJmtYBUjcSghD9LWn+yRLwOxhfQVjm0cBwIt5R/yPF/qC76yIVuWUtM5Y2/zJR1J8OF | ||
532 | 362 | qWchvlImHtvDzS9FQeLyzJAOjvZ2CnWp2gILgUz0WQdOk1Dq8ax7KS9BQ42zxw9EZAEPw3PEFqR | ||
533 | 363 | IQsRTONp+iVS8YxSmoYZjDlCgRMWUmawez/Fv5b9Fb/XkO5Eq4e+KfrpUujXItaipb+tV8h5v3t | ||
534 | 364 | oG3Ie3WOHrVjCLXIdYslpL1O4nadqR6Xv58pHj6k""") | ||
535 | 365 | |||
536 | 366 | test_assertions = [ACCOUNT_ASSERTION, SYSTEM_USER_ASSERTION] | ||
537 | 367 | |||
538 | 368 | def setUp(self): | ||
539 | 369 | super(TestSnapConfig, self).setUp() | ||
540 | 370 | self.subp = util.subp | ||
541 | 371 | self.new_root = tempfile.mkdtemp() | ||
542 | 372 | self.addCleanup(shutil.rmtree, self.new_root) | ||
543 | 373 | |||
544 | 374 | def _get_cloud(self, distro, metadata=None): | ||
545 | 375 | self.patchUtils(self.new_root) | ||
546 | 376 | paths = helpers.Paths({}) | ||
547 | 377 | cls = distros.fetch(distro) | ||
548 | 378 | mydist = cls(distro, {}, paths) | ||
549 | 379 | myds = DataSourceNone.DataSourceNone({}, mydist, paths) | ||
550 | 380 | if metadata: | ||
551 | 381 | myds.metadata.update(metadata) | ||
552 | 382 | return cloud.Cloud(myds, paths, {}, mydist, None) | ||
553 | 383 | |||
554 | 384 | @mock.patch('cloudinit.util.write_file') | ||
555 | 385 | @mock.patch('cloudinit.util.subp') | ||
556 | 386 | def test_snap_config_add_assertions(self, msubp, mwrite): | ||
557 | 387 | add_assertions(self.test_assertions) | ||
558 | 388 | |||
559 | 389 | combined = "\n".join(self.test_assertions) | ||
560 | 390 | mwrite.assert_any_call(ASSERTIONS_FILE, combined.encode('utf-8')) | ||
561 | 391 | msubp.assert_called_with(['snap', 'ack', ASSERTIONS_FILE], | ||
562 | 392 | capture=True) | ||
563 | 393 | |||
564 | 394 | def test_snap_config_add_assertions_empty(self): | ||
565 | 395 | self.assertRaises(ValueError, add_assertions, []) | ||
566 | 396 | |||
567 | 397 | def test_add_assertions_nonlist(self): | ||
568 | 398 | self.assertRaises(ValueError, add_assertions, {}) | ||
569 | 399 | |||
570 | 400 | @mock.patch('cloudinit.util.write_file') | ||
571 | 401 | @mock.patch('cloudinit.util.subp') | ||
572 | 402 | def test_snap_config_add_assertions_ack_fails(self, msubp, mwrite): | ||
573 | 403 | msubp.side_effect = [util.ProcessExecutionError("Invalid assertion")] | ||
574 | 404 | self.assertRaises(util.ProcessExecutionError, add_assertions, | ||
575 | 405 | self.test_assertions) | ||
576 | 406 | |||
577 | 407 | @mock.patch('cloudinit.config.cc_snap_config.add_assertions') | ||
578 | 408 | @mock.patch('cloudinit.config.cc_snap_config.util') | ||
579 | 409 | def test_snap_config_handle_no_config(self, mock_util, mock_add): | ||
580 | 410 | cfg = {} | ||
581 | 411 | cc = self._get_cloud('ubuntu') | ||
582 | 412 | cc.distro = mock.MagicMock() | ||
583 | 413 | cc.distro.name = 'ubuntu' | ||
584 | 414 | mock_util.which.return_value = None | ||
585 | 415 | snap_handle('snap_config', cfg, cc, LOG, None) | ||
586 | 416 | mock_add.assert_not_called() | ||
587 | 417 | |||
588 | 418 | def test_snap_config_add_snap_user_no_config(self): | ||
589 | 419 | usercfg = add_snap_user(cfg=None) | ||
590 | 420 | self.assertEqual(usercfg, None) | ||
591 | 421 | |||
592 | 422 | def test_snap_config_add_snap_user_not_dict(self): | ||
593 | 423 | cfg = ['foobar'] | ||
594 | 424 | self.assertRaises(ValueError, add_snap_user, cfg) | ||
595 | 425 | |||
596 | 426 | def test_snap_config_add_snap_user_no_email(self): | ||
597 | 427 | cfg = {'assertions': [], 'known': True} | ||
598 | 428 | usercfg = add_snap_user(cfg=cfg) | ||
599 | 429 | self.assertEqual(usercfg, None) | ||
600 | 430 | |||
601 | 431 | @mock.patch('cloudinit.config.cc_snap_config.util') | ||
602 | 432 | def test_snap_config_add_snap_user_email_only(self, mock_util): | ||
603 | 433 | email = 'janet@planetjanet.org' | ||
604 | 434 | cfg = {'email': email} | ||
605 | 435 | mock_util.which.return_value = None | ||
606 | 436 | mock_util.system_is_snappy.return_value = True | ||
607 | 437 | mock_util.subp.side_effect = [ | ||
608 | 438 | ("false\n", ""), # snap managed | ||
609 | 439 | ] | ||
610 | 440 | |||
611 | 441 | usercfg = add_snap_user(cfg=cfg) | ||
612 | 442 | |||
613 | 443 | self.assertEqual(usercfg, {'snapuser': email, 'known': False}) | ||
614 | 444 | |||
615 | 445 | @mock.patch('cloudinit.config.cc_snap_config.util') | ||
616 | 446 | def test_snap_config_add_snap_user_email_known(self, mock_util): | ||
617 | 447 | email = 'janet@planetjanet.org' | ||
618 | 448 | known = True | ||
619 | 449 | cfg = {'email': email, 'known': known} | ||
620 | 450 | mock_util.which.return_value = None | ||
621 | 451 | mock_util.system_is_snappy.return_value = True | ||
622 | 452 | mock_util.subp.side_effect = [ | ||
623 | 453 | ("false\n", ""), # snap managed | ||
624 | 454 | (self.SYSTEM_USER_ASSERTION, ""), # snap known system-user | ||
625 | 455 | ] | ||
626 | 456 | |||
627 | 457 | usercfg = add_snap_user(cfg=cfg) | ||
628 | 458 | |||
629 | 459 | self.assertEqual(usercfg, {'snapuser': email, 'known': known}) | ||
630 | 460 | |||
631 | 461 | @mock.patch('cloudinit.config.cc_snap_config.add_assertions') | ||
632 | 462 | @mock.patch('cloudinit.config.cc_snap_config.util') | ||
633 | 463 | def test_snap_config_handle_system_not_snappy(self, mock_util, mock_add): | ||
634 | 464 | cfg = {'snappy': {'assertions': self.test_assertions}} | ||
635 | 465 | cc = self._get_cloud('ubuntu') | ||
636 | 466 | cc.distro = mock.MagicMock() | ||
637 | 467 | cc.distro.name = 'ubuntu' | ||
638 | 468 | mock_util.which.return_value = None | ||
639 | 469 | mock_util.system_is_snappy.return_value = False | ||
640 | 470 | |||
641 | 471 | snap_handle('snap_config', cfg, cc, LOG, None) | ||
642 | 472 | |||
643 | 473 | mock_add.assert_not_called() | ||
644 | 474 | |||
645 | 475 | @mock.patch('cloudinit.config.cc_snap_config.add_assertions') | ||
646 | 476 | @mock.patch('cloudinit.config.cc_snap_config.util') | ||
647 | 477 | def test_snap_config_handle_snapuser(self, mock_util, mock_add): | ||
648 | 478 | email = 'janet@planetjanet.org' | ||
649 | 479 | cfg = { | ||
650 | 480 | 'snappy': { | ||
651 | 481 | 'assertions': self.test_assertions, | ||
652 | 482 | 'email': email, | ||
653 | 483 | } | ||
654 | 484 | } | ||
655 | 485 | cc = self._get_cloud('ubuntu') | ||
656 | 486 | cc.distro = mock.MagicMock() | ||
657 | 487 | cc.distro.name = 'ubuntu' | ||
658 | 488 | mock_util.which.return_value = None | ||
659 | 489 | mock_util.system_is_snappy.return_value = True | ||
660 | 490 | mock_util.subp.side_effect = [ | ||
661 | 491 | ("false\n", ""), # snap managed | ||
662 | 492 | ] | ||
663 | 493 | |||
664 | 494 | snap_handle('snap_config', cfg, cc, LOG, None) | ||
665 | 495 | |||
666 | 496 | mock_add.assert_called_with(self.test_assertions) | ||
667 | 497 | usercfg = {'snapuser': email, 'known': False} | ||
668 | 498 | cc.distro.create_user.assert_called_with(email, **usercfg) | ||
669 | 499 | |||
670 | 500 | @mock.patch('cloudinit.config.cc_snap_config.add_assertions') | ||
671 | 501 | @mock.patch('cloudinit.config.cc_snap_config.util') | ||
672 | 502 | def test_snap_config_handle_snapuser_known(self, mock_util, mock_add): | ||
673 | 503 | email = 'janet@planetjanet.org' | ||
674 | 504 | cfg = { | ||
675 | 505 | 'snappy': { | ||
676 | 506 | 'assertions': self.test_assertions, | ||
677 | 507 | 'email': email, | ||
678 | 508 | 'known': True, | ||
679 | 509 | } | ||
680 | 510 | } | ||
681 | 511 | cc = self._get_cloud('ubuntu') | ||
682 | 512 | cc.distro = mock.MagicMock() | ||
683 | 513 | cc.distro.name = 'ubuntu' | ||
684 | 514 | mock_util.which.return_value = None | ||
685 | 515 | mock_util.system_is_snappy.return_value = True | ||
686 | 516 | mock_util.subp.side_effect = [ | ||
687 | 517 | ("false\n", ""), # snap managed | ||
688 | 518 | (self.SYSTEM_USER_ASSERTION, ""), # snap known system-user | ||
689 | 519 | ] | ||
690 | 520 | |||
691 | 521 | snap_handle('snap_config', cfg, cc, LOG, None) | ||
692 | 522 | |||
693 | 523 | mock_add.assert_called_with(self.test_assertions) | ||
694 | 524 | usercfg = {'snapuser': email, 'known': True} | ||
695 | 525 | cc.distro.create_user.assert_called_with(email, **usercfg) | ||
696 | 526 | |||
697 | 527 | @mock.patch('cloudinit.config.cc_snap_config.add_assertions') | ||
698 | 528 | @mock.patch('cloudinit.config.cc_snap_config.util') | ||
699 | 529 | def test_snap_config_handle_snapuser_known_managed(self, mock_util, | ||
700 | 530 | mock_add): | ||
701 | 531 | email = 'janet@planetjanet.org' | ||
702 | 532 | cfg = { | ||
703 | 533 | 'snappy': { | ||
704 | 534 | 'assertions': self.test_assertions, | ||
705 | 535 | 'email': email, | ||
706 | 536 | 'known': True, | ||
707 | 537 | } | ||
708 | 538 | } | ||
709 | 539 | cc = self._get_cloud('ubuntu') | ||
710 | 540 | cc.distro = mock.MagicMock() | ||
711 | 541 | cc.distro.name = 'ubuntu' | ||
712 | 542 | mock_util.which.return_value = None | ||
713 | 543 | mock_util.system_is_snappy.return_value = True | ||
714 | 544 | mock_util.subp.side_effect = [ | ||
715 | 545 | ("true\n", ""), # snap managed | ||
716 | 546 | ] | ||
717 | 547 | |||
718 | 548 | snap_handle('snap_config', cfg, cc, LOG, None) | ||
719 | 549 | |||
720 | 550 | mock_add.assert_called_with(self.test_assertions) | ||
721 | 551 | cc.distro.create_user.assert_not_called() | ||
722 | 552 | |||
723 | 553 | @mock.patch('cloudinit.config.cc_snap_config.add_assertions') | ||
724 | 554 | @mock.patch('cloudinit.config.cc_snap_config.util') | ||
725 | 555 | def test_snap_config_handle_snapuser_known_no_assertion(self, mock_util, | ||
726 | 556 | mock_add): | ||
727 | 557 | email = 'janet@planetjanet.org' | ||
728 | 558 | cfg = { | ||
729 | 559 | 'snappy': { | ||
730 | 560 | 'assertions': [self.ACCOUNT_ASSERTION], | ||
731 | 561 | 'email': email, | ||
732 | 562 | 'known': True, | ||
733 | 563 | } | ||
734 | 564 | } | ||
735 | 565 | cc = self._get_cloud('ubuntu') | ||
736 | 566 | cc.distro = mock.MagicMock() | ||
737 | 567 | cc.distro.name = 'ubuntu' | ||
738 | 568 | mock_util.which.return_value = None | ||
739 | 569 | mock_util.system_is_snappy.return_value = True | ||
740 | 570 | mock_util.subp.side_effect = [ | ||
741 | 571 | ("true\n", ""), # snap managed | ||
742 | 572 | ("", ""), # snap known system-user | ||
743 | 573 | ] | ||
744 | 574 | |||
745 | 575 | snap_handle('snap_config', cfg, cc, LOG, None) | ||
746 | 576 | |||
747 | 577 | mock_add.assert_called_with([self.ACCOUNT_ASSERTION]) | ||
748 | 578 | cc.distro.create_user.assert_not_called() | ||
749 | 579 | |||
750 | 580 | |||
751 | 290 | def makeop_tmpd(tmpd, op, name, config=None, path=None, cfgfile=None): | 581 | def makeop_tmpd(tmpd, op, name, config=None, path=None, cfgfile=None): |
752 | 291 | if cfgfile: | 582 | if cfgfile: |
753 | 292 | cfgfile = os.path.sep.join([tmpd, cfgfile]) | 583 | cfgfile = os.path.sep.join([tmpd, cfgfile]) |
Is this still needed or desired ?