Merge ~chad.smith/cloud-init:cc-landscape-py3-config-fix into cloud-init:master
- Git
- lp:~chad.smith/cloud-init
- cc-landscape-py3-config-fix
- Merge into master
Status: | Merged | ||||||||
---|---|---|---|---|---|---|---|---|---|
Approved by: | Scott Moser | ||||||||
Approved revision: | f32d3748874bf947ba688d52ccda7095d5929515 | ||||||||
Merged at revision: | f831a874021f3d6d24cbe5639a176f416b5436a6 | ||||||||
Proposed branch: | ~chad.smith/cloud-init:cc-landscape-py3-config-fix | ||||||||
Merge into: | cloud-init:master | ||||||||
Diff against target: |
402 lines (+298/-24) 5 files modified
cloudinit/config/cc_landscape.py (+2/-2) cloudinit/config/cc_puppet.py (+18/-15) cloudinit/helpers.py (+7/-7) tests/unittests/test_handler/test_handler_landscape.py (+129/-0) tests/unittests/test_handler/test_handler_puppet.py (+142/-0) |
||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Scott Moser | Approve | ||
Server Team CI bot | continuous-integration | Approve | |
Review via email: mp+329087@code.launchpad.net |
Commit message
Description of the change
cc_landscape & cc_puppet: Fix six.StringIO use in writing configs
Both landscape and puppet modules had issues with the way they wrote /etc/landscape/
The unit tests shed light on a few issues:
- In the cc_landscape module py3 can't provide six.StringIO content to ConfigParser.write, so we need to ue six.BytesIO instead
- In the cc_puppet module, python <= 2.7 doesn't support using six.StringIO as a context manager, so we drop the context manager fanciness and directly outputstream = StringIO() directly.
- The docstring in cc_puppet is fixed to represent that the conf sub-key needs to contain valid puppet section names enclosing each key-value list.
Ryan Harper (raharper) wrote : | # |
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:cffa37df647
https:/
Executed test runs:
SUCCESS: Checkout
SUCCESS: Unit & Style Tests
SUCCESS: Ubuntu LTS: Build
SUCCESS: Ubuntu LTS: Integration
SUCCESS: CentOS 6 & 7: Build & Test
IN_PROGRESS: Declarative: Post Actions
Click here to trigger a rebuild:
https:/
- 8492ee9... by Chad Smith
-
drop unneeded path.exists assert and reformat default_landscape test conf file
Chad Smith (chad.smith) wrote : | # |
comments addressed
Scott Moser (smoser) wrote : | # |
Under
https:/
I pointed to http://
which showed why the fix there was insufficient.
if you've *not* covered what those unit tests cover, please add them.
over all, looks good, some comments inline.
Chad Smith (chad.smith) : | # |
Scott Moser (smoser) wrote : | # |
i dont want to hold getting this in for a test re-work..
if you can show an example of wrap_and_call busted, just open a bug with that, and then lets fix this here.
Scott Moser (smoser) wrote : | # |
s/fix this here/get this in as it is/
- 2870d72... by Chad Smith
-
add newline to end of stringify, use tmp_path in unit tests instead of os.path.join
- a6ac3e4... by Chad Smith
-
drop nested mock.patch context managers in favor of wrap_and_call
- f32d374... by Chad Smith
-
flakes8
Chad Smith (chad.smith) wrote : | # |
Thanks for the patience on this. I figured out the issue I had with my calls to helpers.
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:f32d3748874
https:/
Executed test runs:
SUCCESS: Checkout
SUCCESS: Unit & Style Tests
SUCCESS: Ubuntu LTS: Build
SUCCESS: Ubuntu LTS: Integration
SUCCESS: MAAS Compatability Testing
IN_PROGRESS: Declarative: Post Actions
Click here to trigger a rebuild:
https:/
Preview Diff
1 | diff --git a/cloudinit/config/cc_landscape.py b/cloudinit/config/cc_landscape.py |
2 | index 86b7138..8f9f1ab 100644 |
3 | --- a/cloudinit/config/cc_landscape.py |
4 | +++ b/cloudinit/config/cc_landscape.py |
5 | @@ -57,7 +57,7 @@ The following default client config is provided, but can be overridden:: |
6 | |
7 | import os |
8 | |
9 | -from six import StringIO |
10 | +from six import BytesIO |
11 | |
12 | from configobj import ConfigObj |
13 | |
14 | @@ -109,7 +109,7 @@ def handle(_name, cfg, cloud, log, _args): |
15 | ls_cloudcfg, |
16 | ] |
17 | merged = merge_together(merge_data) |
18 | - contents = StringIO() |
19 | + contents = BytesIO() |
20 | merged.write(contents) |
21 | |
22 | util.ensure_dir(os.path.dirname(LSC_CLIENT_CFG_FILE)) |
23 | diff --git a/cloudinit/config/cc_puppet.py b/cloudinit/config/cc_puppet.py |
24 | index dc11561..28b1d56 100644 |
25 | --- a/cloudinit/config/cc_puppet.py |
26 | +++ b/cloudinit/config/cc_puppet.py |
27 | @@ -15,21 +15,23 @@ This module handles puppet installation and configuration. If the ``puppet`` |
28 | key does not exist in global configuration, no action will be taken. If a |
29 | config entry for ``puppet`` is present, then by default the latest version of |
30 | puppet will be installed. If ``install`` is set to ``false``, puppet will not |
31 | -be installed. However, this may result in an error if puppet is not already |
32 | +be installed. However, this will result in an error if puppet is not already |
33 | present on the system. The version of puppet to be installed can be specified |
34 | under ``version``, and defaults to ``none``, which selects the latest version |
35 | in the repos. If the ``puppet`` config key exists in the config archive, this |
36 | module will attempt to start puppet even if no installation was performed. |
37 | |
38 | -Puppet configuration can be specified under the ``conf`` key. The configuration |
39 | -is specified as a dictionary which is converted into ``<key>=<value>`` format |
40 | -and appended to ``puppet.conf`` under the ``[puppetd]`` section. The |
41 | +Puppet configuration can be specified under the ``conf`` key. The |
42 | +configuration is specified as a dictionary containing high-level ``<section>`` |
43 | +keys and lists of ``<key>=<value>`` pairs within each section. Each section |
44 | +name and ``<key>=<value>`` pair is written directly to ``puppet.conf``. As |
45 | +such, section names should be one of: ``main``, ``master``, ``agent`` or |
46 | +``user`` and keys should be valid puppet configuration options. The |
47 | ``certname`` key supports string substitutions for ``%i`` and ``%f``, |
48 | corresponding to the instance id and fqdn of the machine respectively. |
49 | -If ``ca_cert`` is present under ``conf``, it will not be written to |
50 | -``puppet.conf``, but instead will be used as the puppermaster certificate. |
51 | -It should be specified in pem format as a multi-line string (using the ``|`` |
52 | -yaml notation). |
53 | +If ``ca_cert`` is present, it will not be written to ``puppet.conf``, but |
54 | +instead will be used as the puppermaster certificate. It should be specified |
55 | +in pem format as a multi-line string (using the ``|`` yaml notation). |
56 | |
57 | **Internal name:** ``cc_puppet`` |
58 | |
59 | @@ -43,12 +45,13 @@ yaml notation). |
60 | install: <true/false> |
61 | version: <version> |
62 | conf: |
63 | - server: "puppetmaster.example.org" |
64 | - certname: "%i.%f" |
65 | - ca_cert: | |
66 | - -------BEGIN CERTIFICATE------- |
67 | - <cert data> |
68 | - -------END CERTIFICATE------- |
69 | + agent: |
70 | + server: "puppetmaster.example.org" |
71 | + certname: "%i.%f" |
72 | + ca_cert: | |
73 | + -------BEGIN CERTIFICATE------- |
74 | + <cert data> |
75 | + -------END CERTIFICATE------- |
76 | """ |
77 | |
78 | from six import StringIO |
79 | @@ -127,7 +130,7 @@ def handle(name, cfg, cloud, log, _args): |
80 | util.write_file(PUPPET_SSL_CERT_PATH, cfg) |
81 | util.chownbyname(PUPPET_SSL_CERT_PATH, 'puppet', 'root') |
82 | else: |
83 | - # Iterate throug the config items, we'll use ConfigParser.set |
84 | + # Iterate through the config items, we'll use ConfigParser.set |
85 | # to overwrite or create new items as needed |
86 | for (o, v) in cfg.items(): |
87 | if o == 'certname': |
88 | diff --git a/cloudinit/helpers.py b/cloudinit/helpers.py |
89 | index f01021a..1979cd9 100644 |
90 | --- a/cloudinit/helpers.py |
91 | +++ b/cloudinit/helpers.py |
92 | @@ -13,7 +13,7 @@ from time import time |
93 | import contextlib |
94 | import os |
95 | |
96 | -import six |
97 | +from six import StringIO |
98 | from six.moves.configparser import ( |
99 | NoSectionError, NoOptionError, RawConfigParser) |
100 | |
101 | @@ -441,12 +441,12 @@ class DefaultingConfigParser(RawConfigParser): |
102 | |
103 | def stringify(self, header=None): |
104 | contents = '' |
105 | - with six.StringIO() as outputstream: |
106 | - self.write(outputstream) |
107 | - outputstream.flush() |
108 | - contents = outputstream.getvalue() |
109 | - if header: |
110 | - contents = "\n".join([header, contents]) |
111 | + outputstream = StringIO() |
112 | + self.write(outputstream) |
113 | + outputstream.flush() |
114 | + contents = outputstream.getvalue() |
115 | + if header: |
116 | + contents = '\n'.join([header, contents, '']) |
117 | return contents |
118 | |
119 | # vi: ts=4 expandtab |
120 | diff --git a/tests/unittests/test_handler/test_handler_landscape.py b/tests/unittests/test_handler/test_handler_landscape.py |
121 | new file mode 100644 |
122 | index 0000000..7c247fa |
123 | --- /dev/null |
124 | +++ b/tests/unittests/test_handler/test_handler_landscape.py |
125 | @@ -0,0 +1,129 @@ |
126 | +# This file is part of cloud-init. See LICENSE file for license information. |
127 | + |
128 | +from cloudinit.config import cc_landscape |
129 | +from cloudinit.sources import DataSourceNone |
130 | +from cloudinit import (distros, helpers, cloud, util) |
131 | +from ..helpers import FilesystemMockingTestCase, mock, wrap_and_call |
132 | + |
133 | +from configobj import ConfigObj |
134 | +import logging |
135 | + |
136 | + |
137 | +LOG = logging.getLogger(__name__) |
138 | + |
139 | + |
140 | +class TestLandscape(FilesystemMockingTestCase): |
141 | + |
142 | + with_logs = True |
143 | + |
144 | + def setUp(self): |
145 | + super(TestLandscape, self).setUp() |
146 | + self.new_root = self.tmp_dir() |
147 | + self.conf = self.tmp_path('client.conf', self.new_root) |
148 | + self.default_file = self.tmp_path('default_landscape', self.new_root) |
149 | + |
150 | + def _get_cloud(self, distro): |
151 | + self.patchUtils(self.new_root) |
152 | + paths = helpers.Paths({'templates_dir': self.new_root}) |
153 | + cls = distros.fetch(distro) |
154 | + mydist = cls(distro, {}, paths) |
155 | + myds = DataSourceNone.DataSourceNone({}, mydist, paths) |
156 | + return cloud.Cloud(myds, paths, {}, mydist, None) |
157 | + |
158 | + def test_handler_skips_empty_landscape_cloudconfig(self): |
159 | + """Empty landscape cloud-config section does no work.""" |
160 | + mycloud = self._get_cloud('ubuntu') |
161 | + mycloud.distro = mock.MagicMock() |
162 | + cfg = {'landscape': {}} |
163 | + cc_landscape.handle('notimportant', cfg, mycloud, LOG, None) |
164 | + self.assertFalse(mycloud.distro.install_packages.called) |
165 | + |
166 | + def test_handler_error_on_invalid_landscape_type(self): |
167 | + """Raise an error when landscape configuraiton option is invalid.""" |
168 | + mycloud = self._get_cloud('ubuntu') |
169 | + cfg = {'landscape': 'wrongtype'} |
170 | + with self.assertRaises(RuntimeError) as context_manager: |
171 | + cc_landscape.handle('notimportant', cfg, mycloud, LOG, None) |
172 | + self.assertIn( |
173 | + "'landscape' key existed in config, but not a dict", |
174 | + str(context_manager.exception)) |
175 | + |
176 | + @mock.patch('cloudinit.config.cc_landscape.util') |
177 | + def test_handler_restarts_landscape_client(self, m_util): |
178 | + """handler restarts lansdscape-client after install.""" |
179 | + mycloud = self._get_cloud('ubuntu') |
180 | + cfg = {'landscape': {'client': {}}} |
181 | + wrap_and_call( |
182 | + 'cloudinit.config.cc_landscape', |
183 | + {'LSC_CLIENT_CFG_FILE': {'new': self.conf}}, |
184 | + cc_landscape.handle, 'notimportant', cfg, mycloud, LOG, None) |
185 | + self.assertEqual( |
186 | + [mock.call(['service', 'landscape-client', 'restart'])], |
187 | + m_util.subp.call_args_list) |
188 | + |
189 | + def test_handler_installs_client_and_creates_config_file(self): |
190 | + """Write landscape client.conf and install landscape-client.""" |
191 | + mycloud = self._get_cloud('ubuntu') |
192 | + cfg = {'landscape': {'client': {}}} |
193 | + expected = {'client': { |
194 | + 'log_level': 'info', |
195 | + 'url': 'https://landscape.canonical.com/message-system', |
196 | + 'ping_url': 'http://landscape.canonical.com/ping', |
197 | + 'data_path': '/var/lib/landscape/client'}} |
198 | + mycloud.distro = mock.MagicMock() |
199 | + wrap_and_call( |
200 | + 'cloudinit.config.cc_landscape', |
201 | + {'LSC_CLIENT_CFG_FILE': {'new': self.conf}, |
202 | + 'LS_DEFAULT_FILE': {'new': self.default_file}}, |
203 | + cc_landscape.handle, 'notimportant', cfg, mycloud, LOG, None) |
204 | + self.assertEqual( |
205 | + [mock.call('landscape-client')], |
206 | + mycloud.distro.install_packages.call_args) |
207 | + self.assertEqual(expected, dict(ConfigObj(self.conf))) |
208 | + self.assertIn( |
209 | + 'Wrote landscape config file to {0}'.format(self.conf), |
210 | + self.logs.getvalue()) |
211 | + default_content = util.load_file(self.default_file) |
212 | + self.assertEqual('RUN=1\n', default_content) |
213 | + |
214 | + def test_handler_writes_merged_client_config_file_with_defaults(self): |
215 | + """Merge and write options from LSC_CLIENT_CFG_FILE with defaults.""" |
216 | + # Write existing sparse client.conf file |
217 | + util.write_file(self.conf, '[client]\ncomputer_title = My PC\n') |
218 | + mycloud = self._get_cloud('ubuntu') |
219 | + cfg = {'landscape': {'client': {}}} |
220 | + expected = {'client': { |
221 | + 'log_level': 'info', |
222 | + 'url': 'https://landscape.canonical.com/message-system', |
223 | + 'ping_url': 'http://landscape.canonical.com/ping', |
224 | + 'data_path': '/var/lib/landscape/client', |
225 | + 'computer_title': 'My PC'}} |
226 | + wrap_and_call( |
227 | + 'cloudinit.config.cc_landscape', |
228 | + {'LSC_CLIENT_CFG_FILE': {'new': self.conf}}, |
229 | + cc_landscape.handle, 'notimportant', cfg, mycloud, LOG, None) |
230 | + self.assertEqual(expected, dict(ConfigObj(self.conf))) |
231 | + self.assertIn( |
232 | + 'Wrote landscape config file to {0}'.format(self.conf), |
233 | + self.logs.getvalue()) |
234 | + |
235 | + def test_handler_writes_merged_provided_cloudconfig_with_defaults(self): |
236 | + """Merge and write options from cloud-config options with defaults.""" |
237 | + # Write empty sparse client.conf file |
238 | + util.write_file(self.conf, '') |
239 | + mycloud = self._get_cloud('ubuntu') |
240 | + cfg = {'landscape': {'client': {'computer_title': 'My PC'}}} |
241 | + expected = {'client': { |
242 | + 'log_level': 'info', |
243 | + 'url': 'https://landscape.canonical.com/message-system', |
244 | + 'ping_url': 'http://landscape.canonical.com/ping', |
245 | + 'data_path': '/var/lib/landscape/client', |
246 | + 'computer_title': 'My PC'}} |
247 | + wrap_and_call( |
248 | + 'cloudinit.config.cc_landscape', |
249 | + {'LSC_CLIENT_CFG_FILE': {'new': self.conf}}, |
250 | + cc_landscape.handle, 'notimportant', cfg, mycloud, LOG, None) |
251 | + self.assertEqual(expected, dict(ConfigObj(self.conf))) |
252 | + self.assertIn( |
253 | + 'Wrote landscape config file to {0}'.format(self.conf), |
254 | + self.logs.getvalue()) |
255 | diff --git a/tests/unittests/test_handler/test_handler_puppet.py b/tests/unittests/test_handler/test_handler_puppet.py |
256 | new file mode 100644 |
257 | index 0000000..805c76b |
258 | --- /dev/null |
259 | +++ b/tests/unittests/test_handler/test_handler_puppet.py |
260 | @@ -0,0 +1,142 @@ |
261 | +# This file is part of cloud-init. See LICENSE file for license information. |
262 | + |
263 | +from cloudinit.config import cc_puppet |
264 | +from cloudinit.sources import DataSourceNone |
265 | +from cloudinit import (distros, helpers, cloud, util) |
266 | +from ..helpers import CiTestCase, mock |
267 | + |
268 | +import logging |
269 | + |
270 | + |
271 | +LOG = logging.getLogger(__name__) |
272 | + |
273 | + |
274 | +@mock.patch('cloudinit.config.cc_puppet.util') |
275 | +@mock.patch('cloudinit.config.cc_puppet.os') |
276 | +class TestAutostartPuppet(CiTestCase): |
277 | + |
278 | + with_logs = True |
279 | + |
280 | + def test_wb_autostart_puppet_updates_puppet_default(self, m_os, m_util): |
281 | + """Update /etc/default/puppet to autostart if it exists.""" |
282 | + |
283 | + def _fake_exists(path): |
284 | + return path == '/etc/default/puppet' |
285 | + |
286 | + m_os.path.exists.side_effect = _fake_exists |
287 | + cc_puppet._autostart_puppet(LOG) |
288 | + self.assertEqual( |
289 | + [mock.call(['sed', '-i', '-e', 's/^START=.*/START=yes/', |
290 | + '/etc/default/puppet'], capture=False)], |
291 | + m_util.subp.call_args_list) |
292 | + |
293 | + def test_wb_autostart_pupppet_enables_puppet_systemctl(self, m_os, m_util): |
294 | + """If systemctl is present, enable puppet via systemctl.""" |
295 | + |
296 | + def _fake_exists(path): |
297 | + return path == '/bin/systemctl' |
298 | + |
299 | + m_os.path.exists.side_effect = _fake_exists |
300 | + cc_puppet._autostart_puppet(LOG) |
301 | + expected_calls = [mock.call( |
302 | + ['/bin/systemctl', 'enable', 'puppet.service'], capture=False)] |
303 | + self.assertEqual(expected_calls, m_util.subp.call_args_list) |
304 | + |
305 | + def test_wb_autostart_pupppet_enables_puppet_chkconfig(self, m_os, m_util): |
306 | + """If chkconfig is present, enable puppet via checkcfg.""" |
307 | + |
308 | + def _fake_exists(path): |
309 | + return path == '/sbin/chkconfig' |
310 | + |
311 | + m_os.path.exists.side_effect = _fake_exists |
312 | + cc_puppet._autostart_puppet(LOG) |
313 | + expected_calls = [mock.call( |
314 | + ['/sbin/chkconfig', 'puppet', 'on'], capture=False)] |
315 | + self.assertEqual(expected_calls, m_util.subp.call_args_list) |
316 | + |
317 | + |
318 | +@mock.patch('cloudinit.config.cc_puppet._autostart_puppet') |
319 | +class TestPuppetHandle(CiTestCase): |
320 | + |
321 | + with_logs = True |
322 | + |
323 | + def setUp(self): |
324 | + super(TestPuppetHandle, self).setUp() |
325 | + self.new_root = self.tmp_dir() |
326 | + self.conf = self.tmp_path('puppet.conf') |
327 | + |
328 | + def _get_cloud(self, distro): |
329 | + paths = helpers.Paths({'templates_dir': self.new_root}) |
330 | + cls = distros.fetch(distro) |
331 | + mydist = cls(distro, {}, paths) |
332 | + myds = DataSourceNone.DataSourceNone({}, mydist, paths) |
333 | + return cloud.Cloud(myds, paths, {}, mydist, None) |
334 | + |
335 | + def test_handler_skips_missing_puppet_key_in_cloudconfig(self, m_auto): |
336 | + """Cloud-config containing no 'puppet' key is skipped.""" |
337 | + mycloud = self._get_cloud('ubuntu') |
338 | + cfg = {} |
339 | + cc_puppet.handle('notimportant', cfg, mycloud, LOG, None) |
340 | + self.assertIn( |
341 | + "no 'puppet' configuration found", self.logs.getvalue()) |
342 | + self.assertEqual(0, m_auto.call_count) |
343 | + |
344 | + @mock.patch('cloudinit.config.cc_puppet.util.subp') |
345 | + def test_handler_puppet_config_starts_puppet_service(self, m_subp, m_auto): |
346 | + """Cloud-config 'puppet' configuration starts puppet.""" |
347 | + mycloud = self._get_cloud('ubuntu') |
348 | + cfg = {'puppet': {'install': False}} |
349 | + cc_puppet.handle('notimportant', cfg, mycloud, LOG, None) |
350 | + self.assertEqual(1, m_auto.call_count) |
351 | + self.assertEqual( |
352 | + [mock.call(['service', 'puppet', 'start'], capture=False)], |
353 | + m_subp.call_args_list) |
354 | + |
355 | + @mock.patch('cloudinit.config.cc_puppet.util.subp') |
356 | + def test_handler_empty_puppet_config_installs_puppet(self, m_subp, m_auto): |
357 | + """Cloud-config empty 'puppet' configuration installs latest puppet.""" |
358 | + mycloud = self._get_cloud('ubuntu') |
359 | + mycloud.distro = mock.MagicMock() |
360 | + cfg = {'puppet': {}} |
361 | + cc_puppet.handle('notimportant', cfg, mycloud, LOG, None) |
362 | + self.assertEqual( |
363 | + [mock.call(('puppet', None))], |
364 | + mycloud.distro.install_packages.call_args_list) |
365 | + |
366 | + @mock.patch('cloudinit.config.cc_puppet.util.subp') |
367 | + def test_handler_puppet_config_installs_puppet_on_true(self, m_subp, _): |
368 | + """Cloud-config with 'puppet' key installs when 'install' is True.""" |
369 | + mycloud = self._get_cloud('ubuntu') |
370 | + mycloud.distro = mock.MagicMock() |
371 | + cfg = {'puppet': {'install': True}} |
372 | + cc_puppet.handle('notimportant', cfg, mycloud, LOG, None) |
373 | + self.assertEqual( |
374 | + [mock.call(('puppet', None))], |
375 | + mycloud.distro.install_packages.call_args_list) |
376 | + |
377 | + @mock.patch('cloudinit.config.cc_puppet.util.subp') |
378 | + def test_handler_puppet_config_installs_puppet_version(self, m_subp, _): |
379 | + """Cloud-config 'puppet' configuration can specify a version.""" |
380 | + mycloud = self._get_cloud('ubuntu') |
381 | + mycloud.distro = mock.MagicMock() |
382 | + cfg = {'puppet': {'version': '3.8'}} |
383 | + cc_puppet.handle('notimportant', cfg, mycloud, LOG, None) |
384 | + self.assertEqual( |
385 | + [mock.call(('puppet', '3.8'))], |
386 | + mycloud.distro.install_packages.call_args_list) |
387 | + |
388 | + @mock.patch('cloudinit.config.cc_puppet.util.subp') |
389 | + def test_handler_puppet_config_updates_puppet_conf(self, m_subp, m_auto): |
390 | + """When 'conf' is provided update values in PUPPET_CONF_PATH.""" |
391 | + mycloud = self._get_cloud('ubuntu') |
392 | + cfg = { |
393 | + 'puppet': { |
394 | + 'conf': {'agent': {'server': 'puppetmaster.example.org'}}}} |
395 | + util.write_file(self.conf, '[agent]\nserver = origpuppet\nother = 3') |
396 | + puppet_conf_path = 'cloudinit.config.cc_puppet.PUPPET_CONF_PATH' |
397 | + mycloud.distro = mock.MagicMock() |
398 | + with mock.patch(puppet_conf_path, self.conf): |
399 | + cc_puppet.handle('notimportant', cfg, mycloud, LOG, None) |
400 | + content = util.load_file(self.conf) |
401 | + expected = '[agent]\nserver = puppetmaster.example.org\nother = 3\n\n' |
402 | + self.assertEqual(expected, content) |
Nice work. Couple of questions inline.