Merge ~powersj/cloud-init:feature/cc-uaclient into cloud-init:master
- Git
- lp:~powersj/cloud-init
- feature/cc-uaclient
- Merge into master
Status: | Merged |
---|---|
Approved by: | Chad Smith |
Approved revision: | b05832a5c137048031b4484d6c43509112323ebc |
Merge reported by: | Server Team CI bot |
Merged at revision: | not available |
Proposed branch: | ~powersj/cloud-init:feature/cc-uaclient |
Merge into: | cloud-init:master |
Diff against target: |
724 lines (+307/-265) 2 files modified
cloudinit/config/cc_ubuntu_advantage.py (+116/-109) cloudinit/config/tests/test_ubuntu_advantage.py (+191/-156) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Chad Smith | Approve | ||
Server Team CI bot | continuous-integration | Approve | |
Review via email: mp+365549@code.launchpad.net |
Commit message
ubuntu_advantage: rewrite cloud-config module
ubuntu-
interface. Update cloud-init's config module to accept new
ubuntu_advantage configuration settings.
* Underscores better than hyphens: deprecate 'ubuntu-advantage'
cloud-config key in favor of 'ubuntu_advantage'
* Attach machines with either sso credentials of UA user_token
* Services are enabled by name though an 'enable' list
* Raise warnings if deprecated ubuntu-advantage config keys are
present, or errors if its config we cannott adapt to
Ubuntu Advantage support can now be configured via #cloud-config
with the following yaml:
ubuntu_advantage:
token: 'thisismyubuntu
enable: [esm, fips, livepatch]
Co-Authored-By: Daniel Watkins <email address hidden>
Author: Chad Smith <email address hidden>
Description of the change
Joshua Powers (powersj) wrote : | # |
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:b05832a5c13
https:/
Executed test runs:
SUCCESS: Checkout
SUCCESS: Unit & Style Tests
SUCCESS: Ubuntu LTS: Build
SUCCESS: Ubuntu LTS: Integration
IN_PROGRESS: Declarative: Post Actions
Click here to trigger a rebuild:
https:/
Chad Smith (chad.smith) wrote : | # |
Validated error and success behaviors on both containers and kvm with
#cloud-config
ubuntu_advantage:
token: <MYTOKEN>
enable: [livepatch]
write_files:
- encoding: b64
content: |
IyBVYnVudHU
cHM6Ly9sb2d
dGFnaW5nLmN
CmxvZ19sZXZ
path: /etc/ubuntu-
Chad Smith (chad.smith) wrote : | # |
This is great. thanks for the bump on this and fixups.
Preview Diff
1 | diff --git a/cloudinit/config/cc_ubuntu_advantage.py b/cloudinit/config/cc_ubuntu_advantage.py | |||
2 | index 5e082bd..f488123 100644 | |||
3 | --- a/cloudinit/config/cc_ubuntu_advantage.py | |||
4 | +++ b/cloudinit/config/cc_ubuntu_advantage.py | |||
5 | @@ -1,150 +1,143 @@ | |||
6 | 1 | # Copyright (C) 2018 Canonical Ltd. | ||
7 | 2 | # | ||
8 | 3 | # This file is part of cloud-init. See LICENSE file for license information. | 1 | # This file is part of cloud-init. See LICENSE file for license information. |
9 | 4 | 2 | ||
11 | 5 | """Ubuntu advantage: manage ubuntu-advantage offerings from Canonical.""" | 3 | """ubuntu_advantage: Configure Ubuntu Advantage support services""" |
12 | 6 | 4 | ||
13 | 7 | import sys | ||
14 | 8 | from textwrap import dedent | 5 | from textwrap import dedent |
15 | 9 | 6 | ||
17 | 10 | from cloudinit import log as logging | 7 | import six |
18 | 8 | |||
19 | 11 | from cloudinit.config.schema import ( | 9 | from cloudinit.config.schema import ( |
20 | 12 | get_schema_doc, validate_cloudconfig_schema) | 10 | get_schema_doc, validate_cloudconfig_schema) |
21 | 11 | from cloudinit import log as logging | ||
22 | 13 | from cloudinit.settings import PER_INSTANCE | 12 | from cloudinit.settings import PER_INSTANCE |
23 | 14 | from cloudinit.subp import prepend_base_command | ||
24 | 15 | from cloudinit import util | 13 | from cloudinit import util |
25 | 16 | 14 | ||
26 | 17 | 15 | ||
29 | 18 | distros = ['ubuntu'] | 16 | UA_URL = 'https://ubuntu.com/advantage' |
28 | 19 | frequency = PER_INSTANCE | ||
30 | 20 | 17 | ||
32 | 21 | LOG = logging.getLogger(__name__) | 18 | distros = ['ubuntu'] |
33 | 22 | 19 | ||
34 | 23 | schema = { | 20 | schema = { |
35 | 24 | 'id': 'cc_ubuntu_advantage', | 21 | 'id': 'cc_ubuntu_advantage', |
36 | 25 | 'name': 'Ubuntu Advantage', | 22 | 'name': 'Ubuntu Advantage', |
38 | 26 | 'title': 'Install, configure and manage ubuntu-advantage offerings', | 23 | 'title': 'Configure Ubuntu Advantage support services', |
39 | 27 | 'description': dedent("""\ | 24 | 'description': dedent("""\ |
61 | 28 | This module provides configuration options to setup ubuntu-advantage | 25 | Attach machine to an existing Ubuntu Advantage support contract and |
62 | 29 | subscriptions. | 26 | enable or disable support services such as Livepatch, ESM, |
63 | 30 | 27 | FIPS and FIPS Updates. When attaching a machine to Ubuntu Advantage, | |
64 | 31 | .. note:: | 28 | one can also specify services to enable. When the 'enable' |
65 | 32 | Both ``commands`` value can be either a dictionary or a list. If | 29 | list is present, any named service will be enabled and all absent |
66 | 33 | the configuration provided is a dictionary, the keys are only used | 30 | services will remain disabled. |
67 | 34 | to order the execution of the commands and the dictionary is | 31 | |
68 | 35 | merged with any vendor-data ubuntu-advantage configuration | 32 | Note that when enabling FIPS or FIPS updates you will need to schedule |
69 | 36 | provided. If a ``commands`` is provided as a list, any vendor-data | 33 | a reboot to ensure the machine is running the FIPS-compliant kernel. |
70 | 37 | ubuntu-advantage ``commands`` are ignored. | 34 | See :ref:`Power State Change` for information on how to configure |
71 | 38 | 35 | cloud-init to perform this reboot. | |
51 | 39 | Ubuntu-advantage ``commands`` is a dictionary or list of | ||
52 | 40 | ubuntu-advantage commands to run on the deployed machine. | ||
53 | 41 | These commands can be used to enable or disable subscriptions to | ||
54 | 42 | various ubuntu-advantage products. See 'man ubuntu-advantage' for more | ||
55 | 43 | information on supported subcommands. | ||
56 | 44 | |||
57 | 45 | .. note:: | ||
58 | 46 | Each command item can be a string or list. If the item is a list, | ||
59 | 47 | 'ubuntu-advantage' can be omitted and it will automatically be | ||
60 | 48 | inserted as part of the command. | ||
72 | 49 | """), | 36 | """), |
73 | 50 | 'distros': distros, | 37 | 'distros': distros, |
74 | 51 | 'examples': [dedent("""\ | 38 | 'examples': [dedent("""\ |
76 | 52 | # Enable Extended Security Maintenance using your service auth token | 39 | # Attach the machine to a Ubuntu Advantage support contract with a |
77 | 40 | # UA contract token obtained from %s. | ||
78 | 41 | ubuntu_advantage: | ||
79 | 42 | token: <ua_contract_token> | ||
80 | 43 | """ % UA_URL), dedent("""\ | ||
81 | 44 | # Attach the machine to an Ubuntu Advantage support contract enabling | ||
82 | 45 | # only fips and esm services. Services will only be enabled if | ||
83 | 46 | # the environment supports said service. Otherwise warnings will | ||
84 | 47 | # be logged for incompatible services specified. | ||
85 | 53 | ubuntu-advantage: | 48 | ubuntu-advantage: |
88 | 54 | commands: | 49 | token: <ua_contract_token> |
89 | 55 | 00: ubuntu-advantage enable-esm <token> | 50 | enable: |
90 | 51 | - fips | ||
91 | 52 | - esm | ||
92 | 56 | """), dedent("""\ | 53 | """), dedent("""\ |
94 | 57 | # Enable livepatch by providing your livepatch token | 54 | # Attach the machine to an Ubuntu Advantage support contract and enable |
95 | 55 | # the FIPS service. Perform a reboot once cloud-init has | ||
96 | 56 | # completed. | ||
97 | 57 | power_state: | ||
98 | 58 | mode: reboot | ||
99 | 58 | ubuntu-advantage: | 59 | ubuntu-advantage: |
115 | 59 | commands: | 60 | token: <ua_contract_token> |
116 | 60 | 00: ubuntu-advantage enable-livepatch <livepatch-token> | 61 | enable: |
117 | 61 | 62 | - fips | |
118 | 62 | """), dedent("""\ | 63 | """)], |
104 | 63 | # Convenience: the ubuntu-advantage command can be omitted when | ||
105 | 64 | # specifying commands as a list and 'ubuntu-advantage' will | ||
106 | 65 | # automatically be prepended. | ||
107 | 66 | # The following commands are equivalent | ||
108 | 67 | ubuntu-advantage: | ||
109 | 68 | commands: | ||
110 | 69 | 00: ['enable-livepatch', 'my-token'] | ||
111 | 70 | 01: ['ubuntu-advantage', 'enable-livepatch', 'my-token'] | ||
112 | 71 | 02: ubuntu-advantage enable-livepatch my-token | ||
113 | 72 | 03: 'ubuntu-advantage enable-livepatch my-token' | ||
114 | 73 | """)], | ||
119 | 74 | 'frequency': PER_INSTANCE, | 64 | 'frequency': PER_INSTANCE, |
120 | 75 | 'type': 'object', | 65 | 'type': 'object', |
121 | 76 | 'properties': { | 66 | 'properties': { |
123 | 77 | 'ubuntu-advantage': { | 67 | 'ubuntu_advantage': { |
124 | 78 | 'type': 'object', | 68 | 'type': 'object', |
125 | 79 | 'properties': { | 69 | 'properties': { |
136 | 80 | 'commands': { | 70 | 'enable': { |
137 | 81 | 'type': ['object', 'array'], # Array of strings or dict | 71 | 'type': 'array', |
138 | 82 | 'items': { | 72 | 'items': {'type': 'string'}, |
139 | 83 | 'oneOf': [ | 73 | }, |
140 | 84 | {'type': 'array', 'items': {'type': 'string'}}, | 74 | 'token': { |
141 | 85 | {'type': 'string'}] | 75 | 'type': 'string', |
142 | 86 | }, | 76 | 'description': ( |
143 | 87 | 'additionalItems': False, # Reject non-string & non-list | 77 | 'A contract token obtained from %s.' % UA_URL) |
134 | 88 | 'minItems': 1, | ||
135 | 89 | 'minProperties': 1, | ||
144 | 90 | } | 78 | } |
145 | 91 | }, | 79 | }, |
148 | 92 | 'additionalProperties': False, # Reject keys not in schema | 80 | 'required': ['token'], |
149 | 93 | 'required': ['commands'] | 81 | 'additionalProperties': False |
150 | 94 | } | 82 | } |
151 | 95 | } | 83 | } |
152 | 96 | } | 84 | } |
153 | 97 | 85 | ||
154 | 98 | # TODO schema for 'assertions' and 'commands' are too permissive at the moment. | ||
155 | 99 | # Once python-jsonschema supports schema draft 6 add support for arbitrary | ||
156 | 100 | # object keys with 'patternProperties' constraint to validate string values. | ||
157 | 101 | |||
158 | 102 | __doc__ = get_schema_doc(schema) # Supplement python help() | 86 | __doc__ = get_schema_doc(schema) # Supplement python help() |
159 | 103 | 87 | ||
165 | 104 | UA_CMD = "ubuntu-advantage" | 88 | LOG = logging.getLogger(__name__) |
161 | 105 | |||
162 | 106 | |||
163 | 107 | def run_commands(commands): | ||
164 | 108 | """Run the commands provided in ubuntu-advantage:commands config. | ||
166 | 109 | 89 | ||
167 | 110 | Commands are run individually. Any errors are collected and reported | ||
168 | 111 | after attempting all commands. | ||
169 | 112 | 90 | ||
198 | 113 | @param commands: A list or dict containing commands to run. Keys of a | 91 | def configure_ua(token=None, enable=None): |
199 | 114 | dict will be used to order the commands provided as dict values. | 92 | """Call ua commandline client to attach or enable services.""" |
200 | 115 | """ | 93 | error = None |
201 | 116 | if not commands: | 94 | if not token: |
202 | 117 | return | 95 | error = ('ubuntu_advantage: token must be provided') |
203 | 118 | LOG.debug('Running user-provided ubuntu-advantage commands') | 96 | LOG.error(error) |
204 | 119 | if isinstance(commands, dict): | 97 | raise RuntimeError(error) |
205 | 120 | # Sort commands based on dictionary key | 98 | |
206 | 121 | commands = [v for _, v in sorted(commands.items())] | 99 | if enable is None: |
207 | 122 | elif not isinstance(commands, list): | 100 | enable = [] |
208 | 123 | raise TypeError( | 101 | elif isinstance(enable, six.string_types): |
209 | 124 | 'commands parameter was not a list or dict: {commands}'.format( | 102 | LOG.warning('ubuntu_advantage: enable should be a list, not' |
210 | 125 | commands=commands)) | 103 | ' a string; treating as a single enable') |
211 | 126 | 104 | enable = [enable] | |
212 | 127 | fixed_ua_commands = prepend_base_command('ubuntu-advantage', commands) | 105 | elif not isinstance(enable, list): |
213 | 128 | 106 | LOG.warning('ubuntu_advantage: enable should be a list, not' | |
214 | 129 | cmd_failures = [] | 107 | ' a %s; skipping enabling services', |
215 | 130 | for command in fixed_ua_commands: | 108 | type(enable).__name__) |
216 | 131 | shell = isinstance(command, str) | 109 | enable = [] |
217 | 132 | try: | 110 | |
218 | 133 | util.subp(command, shell=shell, status_cb=sys.stderr.write) | 111 | attach_cmd = ['ua', 'attach', token] |
219 | 134 | except util.ProcessExecutionError as e: | 112 | LOG.debug('Attaching to Ubuntu Advantage. %s', ' '.join(attach_cmd)) |
220 | 135 | cmd_failures.append(str(e)) | 113 | try: |
221 | 136 | if cmd_failures: | 114 | util.subp(attach_cmd) |
222 | 137 | msg = ( | 115 | except util.ProcessExecutionError as e: |
223 | 138 | 'Failures running ubuntu-advantage commands:\n' | 116 | msg = 'Failure attaching Ubuntu Advantage:\n{error}'.format( |
224 | 139 | '{cmd_failures}'.format( | 117 | error=str(e)) |
197 | 140 | cmd_failures=cmd_failures)) | ||
225 | 141 | util.logexc(LOG, msg) | 118 | util.logexc(LOG, msg) |
226 | 142 | raise RuntimeError(msg) | 119 | raise RuntimeError(msg) |
227 | 120 | enable_errors = [] | ||
228 | 121 | for service in enable: | ||
229 | 122 | try: | ||
230 | 123 | cmd = ['ua', 'enable', service] | ||
231 | 124 | util.subp(cmd, capture=True) | ||
232 | 125 | except util.ProcessExecutionError as e: | ||
233 | 126 | enable_errors.append((service, e)) | ||
234 | 127 | if enable_errors: | ||
235 | 128 | for service, error in enable_errors: | ||
236 | 129 | msg = 'Failure enabling "{service}":\n{error}'.format( | ||
237 | 130 | service=service, error=str(error)) | ||
238 | 131 | util.logexc(LOG, msg) | ||
239 | 132 | raise RuntimeError( | ||
240 | 133 | 'Failure enabling Ubuntu Advantage service(s): {}'.format( | ||
241 | 134 | ', '.join('"{}"'.format(service) | ||
242 | 135 | for service, _ in enable_errors))) | ||
243 | 143 | 136 | ||
244 | 144 | 137 | ||
245 | 145 | def maybe_install_ua_tools(cloud): | 138 | def maybe_install_ua_tools(cloud): |
246 | 146 | """Install ubuntu-advantage-tools if not present.""" | 139 | """Install ubuntu-advantage-tools if not present.""" |
248 | 147 | if util.which('ubuntu-advantage'): | 140 | if util.which('ua'): |
249 | 148 | return | 141 | return |
250 | 149 | try: | 142 | try: |
251 | 150 | cloud.distro.update_package_sources() | 143 | cloud.distro.update_package_sources() |
252 | @@ -159,14 +152,28 @@ def maybe_install_ua_tools(cloud): | |||
253 | 159 | 152 | ||
254 | 160 | 153 | ||
255 | 161 | def handle(name, cfg, cloud, log, args): | 154 | def handle(name, cfg, cloud, log, args): |
260 | 162 | cfgin = cfg.get('ubuntu-advantage') | 155 | ua_section = None |
261 | 163 | if cfgin is None: | 156 | if 'ubuntu-advantage' in cfg: |
262 | 164 | LOG.debug(("Skipping module named %s," | 157 | LOG.warning('Deprecated configuration key "ubuntu-advantage" provided.' |
263 | 165 | " no 'ubuntu-advantage' key in configuration"), name) | 158 | ' Expected underscore delimited "ubuntu_advantage"; will' |
264 | 159 | ' attempt to continue.') | ||
265 | 160 | ua_section = cfg['ubuntu-advantage'] | ||
266 | 161 | if 'ubuntu_advantage' in cfg: | ||
267 | 162 | ua_section = cfg['ubuntu_advantage'] | ||
268 | 163 | if ua_section is None: | ||
269 | 164 | LOG.debug("Skipping module named %s," | ||
270 | 165 | " no 'ubuntu_advantage' configuration found", name) | ||
271 | 166 | return | 166 | return |
272 | 167 | |||
273 | 168 | validate_cloudconfig_schema(cfg, schema) | 167 | validate_cloudconfig_schema(cfg, schema) |
274 | 168 | if 'commands' in ua_section: | ||
275 | 169 | msg = ( | ||
276 | 170 | 'Deprecated configuration "ubuntu-advantage: commands" provided.' | ||
277 | 171 | ' Expected "token"') | ||
278 | 172 | LOG.error(msg) | ||
279 | 173 | raise RuntimeError(msg) | ||
280 | 174 | |||
281 | 169 | maybe_install_ua_tools(cloud) | 175 | maybe_install_ua_tools(cloud) |
283 | 170 | run_commands(cfgin.get('commands', [])) | 176 | configure_ua(token=ua_section.get('token'), |
284 | 177 | enable=ua_section.get('enable')) | ||
285 | 171 | 178 | ||
286 | 172 | # vi: ts=4 expandtab | 179 | # vi: ts=4 expandtab |
287 | diff --git a/cloudinit/config/tests/test_ubuntu_advantage.py b/cloudinit/config/tests/test_ubuntu_advantage.py | |||
288 | index b7cf9be..8c4161e 100644 | |||
289 | --- a/cloudinit/config/tests/test_ubuntu_advantage.py | |||
290 | +++ b/cloudinit/config/tests/test_ubuntu_advantage.py | |||
291 | @@ -1,10 +1,7 @@ | |||
292 | 1 | # This file is part of cloud-init. See LICENSE file for license information. | 1 | # This file is part of cloud-init. See LICENSE file for license information. |
293 | 2 | 2 | ||
294 | 3 | import re | ||
295 | 4 | from six import StringIO | ||
296 | 5 | |||
297 | 6 | from cloudinit.config.cc_ubuntu_advantage import ( | 3 | from cloudinit.config.cc_ubuntu_advantage import ( |
299 | 7 | handle, maybe_install_ua_tools, run_commands, schema) | 4 | configure_ua, handle, maybe_install_ua_tools, schema) |
300 | 8 | from cloudinit.config.schema import validate_cloudconfig_schema | 5 | from cloudinit.config.schema import validate_cloudconfig_schema |
301 | 9 | from cloudinit import util | 6 | from cloudinit import util |
302 | 10 | from cloudinit.tests.helpers import ( | 7 | from cloudinit.tests.helpers import ( |
303 | @@ -20,90 +17,120 @@ class FakeCloud(object): | |||
304 | 20 | self.distro = distro | 17 | self.distro = distro |
305 | 21 | 18 | ||
306 | 22 | 19 | ||
308 | 23 | class TestRunCommands(CiTestCase): | 20 | class TestConfigureUA(CiTestCase): |
309 | 24 | 21 | ||
310 | 25 | with_logs = True | 22 | with_logs = True |
311 | 26 | allowed_subp = [CiTestCase.SUBP_SHELL_TRUE] | 23 | allowed_subp = [CiTestCase.SUBP_SHELL_TRUE] |
312 | 27 | 24 | ||
313 | 28 | def setUp(self): | 25 | def setUp(self): |
315 | 29 | super(TestRunCommands, self).setUp() | 26 | super(TestConfigureUA, self).setUp() |
316 | 30 | self.tmp = self.tmp_dir() | 27 | self.tmp = self.tmp_dir() |
317 | 31 | 28 | ||
318 | 32 | @mock.patch('%s.util.subp' % MPATH) | 29 | @mock.patch('%s.util.subp' % MPATH) |
329 | 33 | def test_run_commands_on_empty_list(self, m_subp): | 30 | def test_configure_ua_attach_error(self, m_subp): |
330 | 34 | """When provided with an empty list, run_commands does nothing.""" | 31 | """Errors from ua attach command are raised.""" |
331 | 35 | run_commands([]) | 32 | m_subp.side_effect = util.ProcessExecutionError( |
332 | 36 | self.assertEqual('', self.logs.getvalue()) | 33 | 'Invalid token SomeToken') |
333 | 37 | m_subp.assert_not_called() | 34 | with self.assertRaises(RuntimeError) as context_manager: |
334 | 38 | 35 | configure_ua(token='SomeToken') | |
325 | 39 | def test_run_commands_on_non_list_or_dict(self): | ||
326 | 40 | """When provided an invalid type, run_commands raises an error.""" | ||
327 | 41 | with self.assertRaises(TypeError) as context_manager: | ||
328 | 42 | run_commands(commands="I'm Not Valid") | ||
335 | 43 | self.assertEqual( | 36 | self.assertEqual( |
337 | 44 | "commands parameter was not a list or dict: I'm Not Valid", | 37 | 'Failure attaching Ubuntu Advantage:\nUnexpected error while' |
338 | 38 | ' running command.\nCommand: -\nExit code: -\nReason: -\n' | ||
339 | 39 | 'Stdout: Invalid token SomeToken\nStderr: -', | ||
340 | 45 | str(context_manager.exception)) | 40 | str(context_manager.exception)) |
341 | 46 | 41 | ||
378 | 47 | def test_run_command_logs_commands_and_exit_codes_to_stderr(self): | 42 | @mock.patch('%s.util.subp' % MPATH) |
379 | 48 | """All exit codes are logged to stderr.""" | 43 | def test_configure_ua_attach_with_token(self, m_subp): |
380 | 49 | outfile = self.tmp_path('output.log', dir=self.tmp) | 44 | """When token is provided, attach the machine to ua using the token.""" |
381 | 50 | 45 | configure_ua(token='SomeToken') | |
382 | 51 | cmd1 = 'echo "HI" >> %s' % outfile | 46 | m_subp.assert_called_once_with(['ua', 'attach', 'SomeToken']) |
383 | 52 | cmd2 = 'bogus command' | 47 | self.assertEqual( |
384 | 53 | cmd3 = 'echo "MOM" >> %s' % outfile | 48 | 'DEBUG: Attaching to Ubuntu Advantage. ua attach SomeToken\n', |
385 | 54 | commands = [cmd1, cmd2, cmd3] | 49 | self.logs.getvalue()) |
386 | 55 | 50 | ||
387 | 56 | mock_path = '%s.sys.stderr' % MPATH | 51 | @mock.patch('%s.util.subp' % MPATH) |
388 | 57 | with mock.patch(mock_path, new_callable=StringIO) as m_stderr: | 52 | def test_configure_ua_attach_on_service_error(self, m_subp): |
389 | 58 | with self.assertRaises(RuntimeError) as context_manager: | 53 | """all services should be enabled and then any failures raised""" |
354 | 59 | run_commands(commands=commands) | ||
355 | 60 | |||
356 | 61 | self.assertIsNotNone( | ||
357 | 62 | re.search(r'bogus: (command )?not found', | ||
358 | 63 | str(context_manager.exception)), | ||
359 | 64 | msg='Expected bogus command not found') | ||
360 | 65 | expected_stderr_log = '\n'.join([ | ||
361 | 66 | 'Begin run command: {cmd}'.format(cmd=cmd1), | ||
362 | 67 | 'End run command: exit(0)', | ||
363 | 68 | 'Begin run command: {cmd}'.format(cmd=cmd2), | ||
364 | 69 | 'ERROR: End run command: exit(127)', | ||
365 | 70 | 'Begin run command: {cmd}'.format(cmd=cmd3), | ||
366 | 71 | 'End run command: exit(0)\n']) | ||
367 | 72 | self.assertEqual(expected_stderr_log, m_stderr.getvalue()) | ||
368 | 73 | |||
369 | 74 | def test_run_command_as_lists(self): | ||
370 | 75 | """When commands are specified as a list, run them in order.""" | ||
371 | 76 | outfile = self.tmp_path('output.log', dir=self.tmp) | ||
372 | 77 | |||
373 | 78 | cmd1 = 'echo "HI" >> %s' % outfile | ||
374 | 79 | cmd2 = 'echo "MOM" >> %s' % outfile | ||
375 | 80 | commands = [cmd1, cmd2] | ||
376 | 81 | with mock.patch('%s.sys.stderr' % MPATH, new_callable=StringIO): | ||
377 | 82 | run_commands(commands=commands) | ||
390 | 83 | 54 | ||
391 | 55 | def fake_subp(cmd, capture=None): | ||
392 | 56 | fail_cmds = [['ua', 'enable', svc] for svc in ['esm', 'cc']] | ||
393 | 57 | if cmd in fail_cmds and capture: | ||
394 | 58 | svc = cmd[-1] | ||
395 | 59 | raise util.ProcessExecutionError( | ||
396 | 60 | 'Invalid {} credentials'.format(svc.upper())) | ||
397 | 61 | |||
398 | 62 | m_subp.side_effect = fake_subp | ||
399 | 63 | |||
400 | 64 | with self.assertRaises(RuntimeError) as context_manager: | ||
401 | 65 | configure_ua(token='SomeToken', enable=['esm', 'cc', 'fips']) | ||
402 | 66 | self.assertEqual( | ||
403 | 67 | m_subp.call_args_list, | ||
404 | 68 | [mock.call(['ua', 'attach', 'SomeToken']), | ||
405 | 69 | mock.call(['ua', 'enable', 'esm'], capture=True), | ||
406 | 70 | mock.call(['ua', 'enable', 'cc'], capture=True), | ||
407 | 71 | mock.call(['ua', 'enable', 'fips'], capture=True)]) | ||
408 | 84 | self.assertIn( | 72 | self.assertIn( |
410 | 85 | 'DEBUG: Running user-provided ubuntu-advantage commands', | 73 | 'WARNING: Failure enabling "esm":\nUnexpected error' |
411 | 74 | ' while running command.\nCommand: -\nExit code: -\nReason: -\n' | ||
412 | 75 | 'Stdout: Invalid ESM credentials\nStderr: -\n', | ||
413 | 86 | self.logs.getvalue()) | 76 | self.logs.getvalue()) |
414 | 87 | self.assertEqual('HI\nMOM\n', util.load_file(outfile)) | ||
415 | 88 | self.assertIn( | 77 | self.assertIn( |
418 | 89 | 'WARNING: Non-ubuntu-advantage commands in ubuntu-advantage' | 78 | 'WARNING: Failure enabling "cc":\nUnexpected error' |
419 | 90 | ' config:', | 79 | ' while running command.\nCommand: -\nExit code: -\nReason: -\n' |
420 | 80 | 'Stdout: Invalid CC credentials\nStderr: -\n', | ||
421 | 81 | self.logs.getvalue()) | ||
422 | 82 | self.assertEqual( | ||
423 | 83 | 'Failure enabling Ubuntu Advantage service(s): "esm", "cc"', | ||
424 | 84 | str(context_manager.exception)) | ||
425 | 85 | |||
426 | 86 | @mock.patch('%s.util.subp' % MPATH) | ||
427 | 87 | def test_configure_ua_attach_with_empty_services(self, m_subp): | ||
428 | 88 | """When services is an empty list, do not auto-enable attach.""" | ||
429 | 89 | configure_ua(token='SomeToken', enable=[]) | ||
430 | 90 | m_subp.assert_called_once_with(['ua', 'attach', 'SomeToken']) | ||
431 | 91 | self.assertEqual( | ||
432 | 92 | 'DEBUG: Attaching to Ubuntu Advantage. ua attach SomeToken\n', | ||
433 | 91 | self.logs.getvalue()) | 93 | self.logs.getvalue()) |
434 | 92 | 94 | ||
443 | 93 | def test_run_command_dict_sorted_as_command_script(self): | 95 | @mock.patch('%s.util.subp' % MPATH) |
444 | 94 | """When commands are a dict, sort them and run.""" | 96 | def test_configure_ua_attach_with_specific_services(self, m_subp): |
445 | 95 | outfile = self.tmp_path('output.log', dir=self.tmp) | 97 | """When services a list, only enable specific services.""" |
446 | 96 | cmd1 = 'echo "HI" >> %s' % outfile | 98 | configure_ua(token='SomeToken', enable=['fips']) |
447 | 97 | cmd2 = 'echo "MOM" >> %s' % outfile | 99 | self.assertEqual( |
448 | 98 | commands = {'02': cmd1, '01': cmd2} | 100 | m_subp.call_args_list, |
449 | 99 | with mock.patch('%s.sys.stderr' % MPATH, new_callable=StringIO): | 101 | [mock.call(['ua', 'attach', 'SomeToken']), |
450 | 100 | run_commands(commands=commands) | 102 | mock.call(['ua', 'enable', 'fips'], capture=True)]) |
451 | 103 | self.assertEqual( | ||
452 | 104 | 'DEBUG: Attaching to Ubuntu Advantage. ua attach SomeToken\n', | ||
453 | 105 | self.logs.getvalue()) | ||
454 | 106 | |||
455 | 107 | @mock.patch('%s.maybe_install_ua_tools' % MPATH, mock.MagicMock()) | ||
456 | 108 | @mock.patch('%s.util.subp' % MPATH) | ||
457 | 109 | def test_configure_ua_attach_with_string_services(self, m_subp): | ||
458 | 110 | """When services a string, treat as singleton list and warn""" | ||
459 | 111 | configure_ua(token='SomeToken', enable='fips') | ||
460 | 112 | self.assertEqual( | ||
461 | 113 | m_subp.call_args_list, | ||
462 | 114 | [mock.call(['ua', 'attach', 'SomeToken']), | ||
463 | 115 | mock.call(['ua', 'enable', 'fips'], capture=True)]) | ||
464 | 116 | self.assertEqual( | ||
465 | 117 | 'WARNING: ubuntu_advantage: enable should be a list, not a' | ||
466 | 118 | ' string; treating as a single enable\n' | ||
467 | 119 | 'DEBUG: Attaching to Ubuntu Advantage. ua attach SomeToken\n', | ||
468 | 120 | self.logs.getvalue()) | ||
469 | 101 | 121 | ||
475 | 102 | expected_messages = [ | 122 | @mock.patch('%s.util.subp' % MPATH) |
476 | 103 | 'DEBUG: Running user-provided ubuntu-advantage commands'] | 123 | def test_configure_ua_attach_with_weird_services(self, m_subp): |
477 | 104 | for message in expected_messages: | 124 | """When services not string or list, warn but still attach""" |
478 | 105 | self.assertIn(message, self.logs.getvalue()) | 125 | configure_ua(token='SomeToken', enable={'deffo': 'wont work'}) |
479 | 106 | self.assertEqual('MOM\nHI\n', util.load_file(outfile)) | 126 | self.assertEqual( |
480 | 127 | m_subp.call_args_list, | ||
481 | 128 | [mock.call(['ua', 'attach', 'SomeToken'])]) | ||
482 | 129 | self.assertEqual( | ||
483 | 130 | 'WARNING: ubuntu_advantage: enable should be a list, not a' | ||
484 | 131 | ' dict; skipping enabling services\n' | ||
485 | 132 | 'DEBUG: Attaching to Ubuntu Advantage. ua attach SomeToken\n', | ||
486 | 133 | self.logs.getvalue()) | ||
487 | 107 | 134 | ||
488 | 108 | 135 | ||
489 | 109 | @skipUnlessJsonSchema() | 136 | @skipUnlessJsonSchema() |
490 | @@ -112,90 +139,50 @@ class TestSchema(CiTestCase, SchemaTestCaseMixin): | |||
491 | 112 | with_logs = True | 139 | with_logs = True |
492 | 113 | schema = schema | 140 | schema = schema |
493 | 114 | 141 | ||
497 | 115 | def test_schema_warns_on_ubuntu_advantage_not_as_dict(self): | 142 | @mock.patch('%s.maybe_install_ua_tools' % MPATH) |
498 | 116 | """If ubuntu-advantage configuration is not a dict, emit a warning.""" | 143 | @mock.patch('%s.configure_ua' % MPATH) |
499 | 117 | validate_cloudconfig_schema({'ubuntu-advantage': 'wrong type'}, schema) | 144 | def test_schema_warns_on_ubuntu_advantage_not_dict(self, _cfg, _): |
500 | 145 | """If ubuntu_advantage configuration is not a dict, emit a warning.""" | ||
501 | 146 | validate_cloudconfig_schema({'ubuntu_advantage': 'wrong type'}, schema) | ||
502 | 118 | self.assertEqual( | 147 | self.assertEqual( |
504 | 119 | "WARNING: Invalid config:\nubuntu-advantage: 'wrong type' is not" | 148 | "WARNING: Invalid config:\nubuntu_advantage: 'wrong type' is not" |
505 | 120 | " of type 'object'\n", | 149 | " of type 'object'\n", |
506 | 121 | self.logs.getvalue()) | 150 | self.logs.getvalue()) |
507 | 122 | 151 | ||
511 | 123 | @mock.patch('%s.run_commands' % MPATH) | 152 | @mock.patch('%s.maybe_install_ua_tools' % MPATH) |
512 | 124 | def test_schema_disallows_unknown_keys(self, _): | 153 | @mock.patch('%s.configure_ua' % MPATH) |
513 | 125 | """Unknown keys in ubuntu-advantage configuration emit warnings.""" | 154 | def test_schema_disallows_unknown_keys(self, _cfg, _): |
514 | 155 | """Unknown keys in ubuntu_advantage configuration emit warnings.""" | ||
515 | 126 | validate_cloudconfig_schema( | 156 | validate_cloudconfig_schema( |
517 | 127 | {'ubuntu-advantage': {'commands': ['ls'], 'invalid-key': ''}}, | 157 | {'ubuntu_advantage': {'token': 'winner', 'invalid-key': ''}}, |
518 | 128 | schema) | 158 | schema) |
519 | 129 | self.assertIn( | 159 | self.assertIn( |
521 | 130 | 'WARNING: Invalid config:\nubuntu-advantage: Additional properties' | 160 | 'WARNING: Invalid config:\nubuntu_advantage: Additional properties' |
522 | 131 | " are not allowed ('invalid-key' was unexpected)", | 161 | " are not allowed ('invalid-key' was unexpected)", |
523 | 132 | self.logs.getvalue()) | 162 | self.logs.getvalue()) |
524 | 133 | 163 | ||
537 | 134 | def test_warn_schema_requires_commands(self): | 164 | @mock.patch('%s.maybe_install_ua_tools' % MPATH) |
538 | 135 | """Warn when ubuntu-advantage configuration lacks commands.""" | 165 | @mock.patch('%s.configure_ua' % MPATH) |
539 | 136 | validate_cloudconfig_schema( | 166 | def test_warn_schema_requires_token(self, _cfg, _): |
540 | 137 | {'ubuntu-advantage': {}}, schema) | 167 | """Warn if ubuntu_advantage configuration lacks token.""" |
529 | 138 | self.assertEqual( | ||
530 | 139 | "WARNING: Invalid config:\nubuntu-advantage: 'commands' is a" | ||
531 | 140 | " required property\n", | ||
532 | 141 | self.logs.getvalue()) | ||
533 | 142 | |||
534 | 143 | @mock.patch('%s.run_commands' % MPATH) | ||
535 | 144 | def test_warn_schema_commands_is_not_list_or_dict(self, _): | ||
536 | 145 | """Warn when ubuntu-advantage:commands config is not a list or dict.""" | ||
541 | 146 | validate_cloudconfig_schema( | 168 | validate_cloudconfig_schema( |
543 | 147 | {'ubuntu-advantage': {'commands': 'broken'}}, schema) | 169 | {'ubuntu_advantage': {'enable': ['esm']}}, schema) |
544 | 148 | self.assertEqual( | 170 | self.assertEqual( |
548 | 149 | "WARNING: Invalid config:\nubuntu-advantage.commands: 'broken' is" | 171 | "WARNING: Invalid config:\nubuntu_advantage:" |
549 | 150 | " not of type 'object', 'array'\n", | 172 | " 'token' is a required property\n", self.logs.getvalue()) |
547 | 151 | self.logs.getvalue()) | ||
550 | 152 | 173 | ||
556 | 153 | @mock.patch('%s.run_commands' % MPATH) | 174 | @mock.patch('%s.maybe_install_ua_tools' % MPATH) |
557 | 154 | def test_warn_schema_when_commands_is_empty(self, _): | 175 | @mock.patch('%s.configure_ua' % MPATH) |
558 | 155 | """Emit warnings when ubuntu-advantage:commands is empty.""" | 176 | def test_warn_schema_services_is_not_list_or_dict(self, _cfg, _): |
559 | 156 | validate_cloudconfig_schema( | 177 | """Warn when ubuntu_advantage:enable config is not a list.""" |
555 | 157 | {'ubuntu-advantage': {'commands': []}}, schema) | ||
560 | 158 | validate_cloudconfig_schema( | 178 | validate_cloudconfig_schema( |
562 | 159 | {'ubuntu-advantage': {'commands': {}}}, schema) | 179 | {'ubuntu_advantage': {'enable': 'needslist'}}, schema) |
563 | 160 | self.assertEqual( | 180 | self.assertEqual( |
567 | 161 | "WARNING: Invalid config:\nubuntu-advantage.commands: [] is too" | 181 | "WARNING: Invalid config:\nubuntu_advantage: 'token' is a" |
568 | 162 | " short\nWARNING: Invalid config:\nubuntu-advantage.commands: {}" | 182 | " required property\nubuntu_advantage.enable: 'needslist'" |
569 | 163 | " does not have enough properties\n", | 183 | " is not of type 'array'\n", |
570 | 164 | self.logs.getvalue()) | 184 | self.logs.getvalue()) |
571 | 165 | 185 | ||
572 | 166 | @mock.patch('%s.run_commands' % MPATH) | ||
573 | 167 | def test_schema_when_commands_are_list_or_dict(self, _): | ||
574 | 168 | """No warnings when ubuntu-advantage:commands are a list or dict.""" | ||
575 | 169 | validate_cloudconfig_schema( | ||
576 | 170 | {'ubuntu-advantage': {'commands': ['valid']}}, schema) | ||
577 | 171 | validate_cloudconfig_schema( | ||
578 | 172 | {'ubuntu-advantage': {'commands': {'01': 'also valid'}}}, schema) | ||
579 | 173 | self.assertEqual('', self.logs.getvalue()) | ||
580 | 174 | |||
581 | 175 | def test_duplicates_are_fine_array_array(self): | ||
582 | 176 | """Duplicated commands array/array entries are allowed.""" | ||
583 | 177 | self.assertSchemaValid( | ||
584 | 178 | {'commands': [["echo", "bye"], ["echo" "bye"]]}, | ||
585 | 179 | "command entries can be duplicate.") | ||
586 | 180 | |||
587 | 181 | def test_duplicates_are_fine_array_string(self): | ||
588 | 182 | """Duplicated commands array/string entries are allowed.""" | ||
589 | 183 | self.assertSchemaValid( | ||
590 | 184 | {'commands': ["echo bye", "echo bye"]}, | ||
591 | 185 | "command entries can be duplicate.") | ||
592 | 186 | |||
593 | 187 | def test_duplicates_are_fine_dict_array(self): | ||
594 | 188 | """Duplicated commands dict/array entries are allowed.""" | ||
595 | 189 | self.assertSchemaValid( | ||
596 | 190 | {'commands': {'00': ["echo", "bye"], '01': ["echo", "bye"]}}, | ||
597 | 191 | "command entries can be duplicate.") | ||
598 | 192 | |||
599 | 193 | def test_duplicates_are_fine_dict_string(self): | ||
600 | 194 | """Duplicated commands dict/string entries are allowed.""" | ||
601 | 195 | self.assertSchemaValid( | ||
602 | 196 | {'commands': {'00': "echo bye", '01': "echo bye"}}, | ||
603 | 197 | "command entries can be duplicate.") | ||
604 | 198 | |||
605 | 199 | 186 | ||
606 | 200 | class TestHandle(CiTestCase): | 187 | class TestHandle(CiTestCase): |
607 | 201 | 188 | ||
608 | @@ -205,41 +192,89 @@ class TestHandle(CiTestCase): | |||
609 | 205 | super(TestHandle, self).setUp() | 192 | super(TestHandle, self).setUp() |
610 | 206 | self.tmp = self.tmp_dir() | 193 | self.tmp = self.tmp_dir() |
611 | 207 | 194 | ||
612 | 208 | @mock.patch('%s.run_commands' % MPATH) | ||
613 | 209 | @mock.patch('%s.validate_cloudconfig_schema' % MPATH) | 195 | @mock.patch('%s.validate_cloudconfig_schema' % MPATH) |
615 | 210 | def test_handle_no_config(self, m_schema, m_run): | 196 | def test_handle_no_config(self, m_schema): |
616 | 211 | """When no ua-related configuration is provided, nothing happens.""" | 197 | """When no ua-related configuration is provided, nothing happens.""" |
617 | 212 | cfg = {} | 198 | cfg = {} |
618 | 213 | handle('ua-test', cfg=cfg, cloud=None, log=self.logger, args=None) | 199 | handle('ua-test', cfg=cfg, cloud=None, log=self.logger, args=None) |
619 | 214 | self.assertIn( | 200 | self.assertIn( |
622 | 215 | "DEBUG: Skipping module named ua-test, no 'ubuntu-advantage' key" | 201 | "DEBUG: Skipping module named ua-test, no 'ubuntu_advantage'" |
623 | 216 | " in config", | 202 | ' configuration found', |
624 | 217 | self.logs.getvalue()) | 203 | self.logs.getvalue()) |
625 | 218 | m_schema.assert_not_called() | 204 | m_schema.assert_not_called() |
626 | 219 | m_run.assert_not_called() | ||
627 | 220 | 205 | ||
628 | 206 | @mock.patch('%s.configure_ua' % MPATH) | ||
629 | 221 | @mock.patch('%s.maybe_install_ua_tools' % MPATH) | 207 | @mock.patch('%s.maybe_install_ua_tools' % MPATH) |
631 | 222 | def test_handle_tries_to_install_ubuntu_advantage_tools(self, m_install): | 208 | def test_handle_tries_to_install_ubuntu_advantage_tools( |
632 | 209 | self, m_install, m_cfg): | ||
633 | 223 | """If ubuntu_advantage is provided, try installing ua-tools package.""" | 210 | """If ubuntu_advantage is provided, try installing ua-tools package.""" |
635 | 224 | cfg = {'ubuntu-advantage': {}} | 211 | cfg = {'ubuntu_advantage': {'token': 'valid'}} |
636 | 225 | mycloud = FakeCloud(None) | 212 | mycloud = FakeCloud(None) |
637 | 226 | handle('nomatter', cfg=cfg, cloud=mycloud, log=self.logger, args=None) | 213 | handle('nomatter', cfg=cfg, cloud=mycloud, log=self.logger, args=None) |
638 | 227 | m_install.assert_called_once_with(mycloud) | 214 | m_install.assert_called_once_with(mycloud) |
639 | 228 | 215 | ||
640 | 216 | @mock.patch('%s.configure_ua' % MPATH) | ||
641 | 229 | @mock.patch('%s.maybe_install_ua_tools' % MPATH) | 217 | @mock.patch('%s.maybe_install_ua_tools' % MPATH) |
645 | 230 | def test_handle_runs_commands_provided(self, m_install): | 218 | def test_handle_passes_credentials_and_services_to_configure_ua( |
646 | 231 | """When commands are specified as a list, run them.""" | 219 | self, m_install, m_configure_ua): |
647 | 232 | outfile = self.tmp_path('output.log', dir=self.tmp) | 220 | """All ubuntu_advantage config keys are passed to configure_ua.""" |
648 | 221 | cfg = {'ubuntu_advantage': {'token': 'token', 'enable': ['esm']}} | ||
649 | 222 | handle('nomatter', cfg=cfg, cloud=None, log=self.logger, args=None) | ||
650 | 223 | m_configure_ua.assert_called_once_with( | ||
651 | 224 | token='token', enable=['esm']) | ||
652 | 225 | |||
653 | 226 | @mock.patch('%s.maybe_install_ua_tools' % MPATH, mock.MagicMock()) | ||
654 | 227 | @mock.patch('%s.configure_ua' % MPATH) | ||
655 | 228 | def test_handle_warns_on_deprecated_ubuntu_advantage_key_w_config( | ||
656 | 229 | self, m_configure_ua): | ||
657 | 230 | """Warning when ubuntu-advantage key is present with new config""" | ||
658 | 231 | cfg = {'ubuntu-advantage': {'token': 'token', 'enable': ['esm']}} | ||
659 | 232 | handle('nomatter', cfg=cfg, cloud=None, log=self.logger, args=None) | ||
660 | 233 | self.assertEqual( | ||
661 | 234 | 'WARNING: Deprecated configuration key "ubuntu-advantage"' | ||
662 | 235 | ' provided. Expected underscore delimited "ubuntu_advantage";' | ||
663 | 236 | ' will attempt to continue.', | ||
664 | 237 | self.logs.getvalue().splitlines()[0]) | ||
665 | 238 | m_configure_ua.assert_called_once_with( | ||
666 | 239 | token='token', enable=['esm']) | ||
667 | 240 | |||
668 | 241 | def test_handle_error_on_deprecated_commands_key_dashed(self): | ||
669 | 242 | """Error when commands is present in ubuntu-advantage key.""" | ||
670 | 243 | cfg = {'ubuntu-advantage': {'commands': 'nogo'}} | ||
671 | 244 | with self.assertRaises(RuntimeError) as context_manager: | ||
672 | 245 | handle('nomatter', cfg=cfg, cloud=None, log=self.logger, args=None) | ||
673 | 246 | self.assertEqual( | ||
674 | 247 | 'Deprecated configuration "ubuntu-advantage: commands" provided.' | ||
675 | 248 | ' Expected "token"', | ||
676 | 249 | str(context_manager.exception)) | ||
677 | 250 | |||
678 | 251 | def test_handle_error_on_deprecated_commands_key_underscored(self): | ||
679 | 252 | """Error when commands is present in ubuntu_advantage key.""" | ||
680 | 253 | cfg = {'ubuntu_advantage': {'commands': 'nogo'}} | ||
681 | 254 | with self.assertRaises(RuntimeError) as context_manager: | ||
682 | 255 | handle('nomatter', cfg=cfg, cloud=None, log=self.logger, args=None) | ||
683 | 256 | self.assertEqual( | ||
684 | 257 | 'Deprecated configuration "ubuntu-advantage: commands" provided.' | ||
685 | 258 | ' Expected "token"', | ||
686 | 259 | str(context_manager.exception)) | ||
687 | 233 | 260 | ||
688 | 261 | @mock.patch('%s.maybe_install_ua_tools' % MPATH, mock.MagicMock()) | ||
689 | 262 | @mock.patch('%s.configure_ua' % MPATH) | ||
690 | 263 | def test_handle_prefers_new_style_config( | ||
691 | 264 | self, m_configure_ua): | ||
692 | 265 | """ubuntu_advantage should be preferred over ubuntu-advantage""" | ||
693 | 234 | cfg = { | 266 | cfg = { |
702 | 235 | 'ubuntu-advantage': {'commands': ['echo "HI" >> %s' % outfile, | 267 | 'ubuntu-advantage': {'token': 'nope', 'enable': ['wrong']}, |
703 | 236 | 'echo "MOM" >> %s' % outfile]}} | 268 | 'ubuntu_advantage': {'token': 'token', 'enable': ['esm']}, |
704 | 237 | mock_path = '%s.sys.stderr' % MPATH | 269 | } |
705 | 238 | with self.allow_subp([CiTestCase.SUBP_SHELL_TRUE]): | 270 | handle('nomatter', cfg=cfg, cloud=None, log=self.logger, args=None) |
706 | 239 | with mock.patch(mock_path, new_callable=StringIO): | 271 | self.assertEqual( |
707 | 240 | handle('nomatter', cfg=cfg, cloud=None, log=self.logger, | 272 | 'WARNING: Deprecated configuration key "ubuntu-advantage"' |
708 | 241 | args=None) | 273 | ' provided. Expected underscore delimited "ubuntu_advantage";' |
709 | 242 | self.assertEqual('HI\nMOM\n', util.load_file(outfile)) | 274 | ' will attempt to continue.', |
710 | 275 | self.logs.getvalue().splitlines()[0]) | ||
711 | 276 | m_configure_ua.assert_called_once_with( | ||
712 | 277 | token='token', enable=['esm']) | ||
713 | 243 | 278 | ||
714 | 244 | 279 | ||
715 | 245 | class TestMaybeInstallUATools(CiTestCase): | 280 | class TestMaybeInstallUATools(CiTestCase): |
716 | @@ -253,7 +288,7 @@ class TestMaybeInstallUATools(CiTestCase): | |||
717 | 253 | @mock.patch('%s.util.which' % MPATH) | 288 | @mock.patch('%s.util.which' % MPATH) |
718 | 254 | def test_maybe_install_ua_tools_noop_when_ua_tools_present(self, m_which): | 289 | def test_maybe_install_ua_tools_noop_when_ua_tools_present(self, m_which): |
719 | 255 | """Do nothing if ubuntu-advantage-tools already exists.""" | 290 | """Do nothing if ubuntu-advantage-tools already exists.""" |
721 | 256 | m_which.return_value = '/usr/bin/ubuntu-advantage' # already installed | 291 | m_which.return_value = '/usr/bin/ua' # already installed |
722 | 257 | distro = mock.MagicMock() | 292 | distro = mock.MagicMock() |
723 | 258 | distro.update_package_sources.side_effect = RuntimeError( | 293 | distro.update_package_sources.side_effect = RuntimeError( |
724 | 259 | 'Some apt error') | 294 | 'Some apt error') |
Diff between Dan's branch and mine https:/ /paste. ubuntu. com/p/v3gwtq7SQ 9/