Merge ~chad.smith/cloud-init:fix/cent-6-jinja2 into cloud-init:master
- Git
- lp:~chad.smith/cloud-init
- fix/cent-6-jinja2
- Merge into master
Status: | Work in progress |
---|---|
Proposed branch: | ~chad.smith/cloud-init:fix/cent-6-jinja2 |
Merge into: | cloud-init:master |
Diff against target: |
675 lines (+124/-118) 15 files modified
cloudinit/cmd/devel/__init__.py (+0/-25) cloudinit/cmd/devel/render.py (+3/-3) cloudinit/cmd/devel/tests/test_render.py (+28/-31) cloudinit/cmd/query.py (+3/-3) cloudinit/cmd/tests/test_clean.py (+1/-1) cloudinit/cmd/tests/test_cloud_id.py (+6/-6) cloudinit/cmd/tests/test_query.py (+34/-37) cloudinit/cmd/tests/test_status.py (+1/-1) cloudinit/helpers.py (+7/-0) cloudinit/log.py (+7/-0) cloudinit/sources/helpers/netlink.py (+4/-0) cloudinit/stages.py (+7/-0) cloudinit/templater.py (+19/-7) tests/unittests/test_builtin_handlers.py (+2/-2) tests/unittests/test_handler/test_schema.py (+2/-2) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Ryan Harper | Needs Fixing | ||
Server Team CI bot | continuous-integration | Approve | |
Chad Smith | Pending | ||
Review via email: mp+360839@code.launchpad.net |
Commit message
Centos 6: Fix jinja rendering and netlink on older python versions.
jinja2.
of jinja2. This just catches that error separately from other
jinja support allowing the code to work.
Also fix netlink behavior stuct.unpack methods expect data as a string.
To improve testing and mocks, relocate the following:
- read_cfg_paths to cloudinit.stages
- addLogHandlerCLI to cloudinit.log
The testcase to recreate was:
./tools/
--artifacts=./rpm/ centos/6
LP: #1795933
Description of the change
see commit message
Server Team CI bot (server-team-bot) wrote : | # |
Scott Moser (smoser) wrote : | # |
no mention of the addLogHanderCLI or read_cfg_paths move in commit message.
the rest of it appears to be test or PY26 only, so i'm good with that.
fix your commit message.
Ryan Harper (raharper) wrote : | # |
I'd like to see this rebased against 18.3 since that's the last PY26 supported release.
Unmerged commits
- b41878c... by Chad Smith
-
on PY26, subytraction is not attempted
- 5dc81f7... by Chad Smith
-
unittest fixups
- c2f465e... by Chad Smith
-
always remember the mock
- 5f8561a... by Chad Smith
-
lint
- e274a19... by Chad Smith
-
devel.read_
cfg_paths -> stages and devel.addLogHan dlerCLI -> log - 4283e1e... by Chad Smith
-
struct.unpack requires str in py26
- b7e7db8... by Chad Smith
-
need to mock leaked getuid for root unittest runs
- 31ef622... by Chad Smith
-
centos: assertraises context_
manager. exception is an int on SystemExit instance - 69677fc... by Scott Moser
-
Take Chad's good fix
- 9271323... by Scott Moser
-
Centos 6: Fix jinja rendering on older jinja versions.
jinja2.
runtime. implements_ to_string is not available in older versions
of jinja2. This just catches that error separately from other
jinja support allowing the code to work.The testcase to recreate was:
./tools/run-container --source-package --package \
--artifacts= ./rpm/ centos/6
Preview Diff
1 | diff --git a/cloudinit/cmd/devel/__init__.py b/cloudinit/cmd/devel/__init__.py |
2 | index 3ae28b6..e69de29 100644 |
3 | --- a/cloudinit/cmd/devel/__init__.py |
4 | +++ b/cloudinit/cmd/devel/__init__.py |
5 | @@ -1,25 +0,0 @@ |
6 | -# This file is part of cloud-init. See LICENSE file for license information. |
7 | - |
8 | -"""Common cloud-init devel commandline utility functions.""" |
9 | - |
10 | - |
11 | -import logging |
12 | - |
13 | -from cloudinit import log |
14 | -from cloudinit.stages import Init |
15 | - |
16 | - |
17 | -def addLogHandlerCLI(logger, log_level): |
18 | - """Add a commandline logging handler to emit messages to stderr.""" |
19 | - formatter = logging.Formatter('%(levelname)s: %(message)s') |
20 | - log.setupBasicLogging(log_level, formatter=formatter) |
21 | - return logger |
22 | - |
23 | - |
24 | -def read_cfg_paths(): |
25 | - """Return a Paths object based on the system configuration on disk.""" |
26 | - init = Init(ds_deps=[]) |
27 | - init.read_cfg() |
28 | - return init.paths |
29 | - |
30 | -# vi: ts=4 expandtab |
31 | diff --git a/cloudinit/cmd/devel/render.py b/cloudinit/cmd/devel/render.py |
32 | index 1bc2240..b93abd4 100755 |
33 | --- a/cloudinit/cmd/devel/render.py |
34 | +++ b/cloudinit/cmd/devel/render.py |
35 | @@ -9,7 +9,7 @@ import sys |
36 | from cloudinit.handlers.jinja_template import render_jinja_payload_from_file |
37 | from cloudinit import log |
38 | from cloudinit.sources import INSTANCE_JSON_FILE, INSTANCE_JSON_SENSITIVE_FILE |
39 | -from . import addLogHandlerCLI, read_cfg_paths |
40 | +from cloudinit import stages |
41 | |
42 | NAME = 'render' |
43 | |
44 | @@ -45,11 +45,11 @@ def handle_args(name, args): |
45 | |
46 | @return 0 on success, 1 on failure. |
47 | """ |
48 | - addLogHandlerCLI(LOG, log.DEBUG if args.debug else log.WARNING) |
49 | + log.addLogHandlerCLI(LOG, log.DEBUG if args.debug else log.WARNING) |
50 | if args.instance_data: |
51 | instance_data_fn = args.instance_data |
52 | else: |
53 | - paths = read_cfg_paths() |
54 | + paths = stages.read_cfg_paths() |
55 | uid = os.getuid() |
56 | redacted_data_fn = os.path.join(paths.run_dir, INSTANCE_JSON_FILE) |
57 | if uid == 0: |
58 | diff --git a/cloudinit/cmd/devel/tests/test_render.py b/cloudinit/cmd/devel/tests/test_render.py |
59 | index 988bba0..19c05c3 100644 |
60 | --- a/cloudinit/cmd/devel/tests/test_render.py |
61 | +++ b/cloudinit/cmd/devel/tests/test_render.py |
62 | @@ -7,7 +7,7 @@ from collections import namedtuple |
63 | from cloudinit.cmd.devel import render |
64 | from cloudinit.helpers import Paths |
65 | from cloudinit.sources import INSTANCE_JSON_FILE, INSTANCE_JSON_SENSITIVE_FILE |
66 | -from cloudinit.tests.helpers import CiTestCase, mock, skipUnlessJinja |
67 | +from cloudinit.tests.helpers import CiTestCase, PY26, mock, skipUnlessJinja |
68 | from cloudinit.util import ensure_dir, write_file |
69 | |
70 | |
71 | @@ -20,6 +20,7 @@ class TestRender(CiTestCase): |
72 | def setUp(self): |
73 | super(TestRender, self).setUp() |
74 | self.tmp = self.tmp_dir() |
75 | + self.add_patch('cloudinit.log.addLogHandlerCLI', 'm_logcli') |
76 | |
77 | def test_handle_args_error_on_missing_user_data(self): |
78 | """When user_data file path does not exist, log an error.""" |
79 | @@ -28,8 +29,7 @@ class TestRender(CiTestCase): |
80 | write_file(instance_data, '{}') |
81 | args = self.args( |
82 | user_data=absent_file, instance_data=instance_data, debug=False) |
83 | - with mock.patch('sys.stderr', new_callable=StringIO): |
84 | - self.assertEqual(1, render.handle_args('anyname', args)) |
85 | + self.assertEqual(1, render.handle_args('anyname', args)) |
86 | self.assertIn( |
87 | 'Missing user-data file: %s' % absent_file, |
88 | self.logs.getvalue()) |
89 | @@ -40,8 +40,7 @@ class TestRender(CiTestCase): |
90 | absent_file = self.tmp_path('instance-data', dir=self.tmp) |
91 | args = self.args( |
92 | user_data=user_data, instance_data=absent_file, debug=False) |
93 | - with mock.patch('sys.stderr', new_callable=StringIO): |
94 | - self.assertEqual(1, render.handle_args('anyname', args)) |
95 | + self.assertEqual(1, render.handle_args('anyname', args)) |
96 | self.assertIn( |
97 | 'Missing instance-data.json file: %s' % absent_file, |
98 | self.logs.getvalue()) |
99 | @@ -52,12 +51,11 @@ class TestRender(CiTestCase): |
100 | run_dir = self.tmp_path('run_dir', dir=self.tmp) |
101 | ensure_dir(run_dir) |
102 | paths = Paths({'run_dir': run_dir}) |
103 | - self.add_patch('cloudinit.cmd.devel.render.read_cfg_paths', 'm_paths') |
104 | + self.add_patch('cloudinit.stages.read_cfg_paths', 'm_paths') |
105 | self.m_paths.return_value = paths |
106 | args = self.args( |
107 | user_data=user_data, instance_data=None, debug=False) |
108 | - with mock.patch('sys.stderr', new_callable=StringIO): |
109 | - self.assertEqual(1, render.handle_args('anyname', args)) |
110 | + self.assertEqual(1, render.handle_args('anyname', args)) |
111 | json_file = os.path.join(run_dir, INSTANCE_JSON_FILE) |
112 | self.assertIn( |
113 | 'Missing instance-data.json file: %s' % json_file, |
114 | @@ -69,14 +67,13 @@ class TestRender(CiTestCase): |
115 | run_dir = self.tmp_path('run_dir', dir=self.tmp) |
116 | ensure_dir(run_dir) |
117 | paths = Paths({'run_dir': run_dir}) |
118 | - self.add_patch('cloudinit.cmd.devel.render.read_cfg_paths', 'm_paths') |
119 | + self.add_patch('cloudinit.stages.read_cfg_paths', 'm_paths') |
120 | self.m_paths.return_value = paths |
121 | args = self.args( |
122 | user_data=user_data, instance_data=None, debug=False) |
123 | - with mock.patch('sys.stderr', new_callable=StringIO): |
124 | - with mock.patch('os.getuid') as m_getuid: |
125 | - m_getuid.return_value = 0 |
126 | - self.assertEqual(1, render.handle_args('anyname', args)) |
127 | + with mock.patch('os.getuid') as m_getuid: |
128 | + m_getuid.return_value = 0 |
129 | + self.assertEqual(1, render.handle_args('anyname', args)) |
130 | json_file = os.path.join(run_dir, INSTANCE_JSON_FILE) |
131 | json_sensitive = os.path.join(run_dir, INSTANCE_JSON_SENSITIVE_FILE) |
132 | self.assertIn( |
133 | @@ -95,15 +92,14 @@ class TestRender(CiTestCase): |
134 | json_sensitive = os.path.join(run_dir, INSTANCE_JSON_SENSITIVE_FILE) |
135 | write_file(json_sensitive, '{"my-var": "jinja worked"}') |
136 | paths = Paths({'run_dir': run_dir}) |
137 | - self.add_patch('cloudinit.cmd.devel.render.read_cfg_paths', 'm_paths') |
138 | + self.add_patch('cloudinit.stages.read_cfg_paths', 'm_paths') |
139 | self.m_paths.return_value = paths |
140 | args = self.args( |
141 | user_data=user_data, instance_data=None, debug=False) |
142 | - with mock.patch('sys.stderr', new_callable=StringIO): |
143 | - with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout: |
144 | - with mock.patch('os.getuid') as m_getuid: |
145 | - m_getuid.return_value = 0 |
146 | - self.assertEqual(0, render.handle_args('anyname', args)) |
147 | + with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout: |
148 | + with mock.patch('os.getuid') as m_getuid: |
149 | + m_getuid.return_value = 0 |
150 | + self.assertEqual(0, render.handle_args('anyname', args)) |
151 | self.assertIn('rendering: jinja worked', m_stdout.getvalue()) |
152 | |
153 | @skipUnlessJinja() |
154 | @@ -115,13 +111,10 @@ class TestRender(CiTestCase): |
155 | write_file(instance_data, '{"my-var": "jinja worked"}') |
156 | args = self.args( |
157 | user_data=user_data, instance_data=instance_data, debug=True) |
158 | - with mock.patch('sys.stderr', new_callable=StringIO) as m_console_err: |
159 | - with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout: |
160 | - self.assertEqual(0, render.handle_args('anyname', args)) |
161 | + with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout: |
162 | + self.assertEqual(0, render.handle_args('anyname', args)) |
163 | self.assertIn( |
164 | 'DEBUG: Converted jinja variables\n{', self.logs.getvalue()) |
165 | - self.assertIn( |
166 | - 'DEBUG: Converted jinja variables\n{', m_console_err.getvalue()) |
167 | self.assertEqual('rendering: jinja worked', m_stdout.getvalue()) |
168 | |
169 | @skipUnlessJinja() |
170 | @@ -133,12 +126,16 @@ class TestRender(CiTestCase): |
171 | write_file(instance_data, '{"my-var": "jinja worked"}') |
172 | args = self.args( |
173 | user_data=user_data, instance_data=instance_data, debug=True) |
174 | - with mock.patch('sys.stderr', new_callable=StringIO): |
175 | - self.assertEqual(1, render.handle_args('anyname', args)) |
176 | - self.assertIn( |
177 | - 'WARNING: Ignoring jinja template for %s: Undefined jinja' |
178 | - ' variable: "my-var". Jinja tried subtraction. Perhaps you meant' |
179 | - ' "my_var"?' % user_data, |
180 | - self.logs.getvalue()) |
181 | + self.assertEqual(1, render.handle_args('anyname', args)) |
182 | + if PY26: |
183 | + msg = ( |
184 | + 'WARNING: Ignoring jinja template for %s: \'var\' is' |
185 | + ' undefined' % user_data) |
186 | + else: |
187 | + msg = ( |
188 | + 'WARNING: Ignoring jinja template for %s: Undefined jinja' |
189 | + ' variable: "my-var". Jinja tried subtraction. Perhaps you' |
190 | + ' meant "my_var"?' % user_data) |
191 | + self.assertIn(msg, self.logs.getvalue()) |
192 | |
193 | # vi: ts=4 expandtab |
194 | diff --git a/cloudinit/cmd/query.py b/cloudinit/cmd/query.py |
195 | index 1d888b9..07d9d5f 100644 |
196 | --- a/cloudinit/cmd/query.py |
197 | +++ b/cloudinit/cmd/query.py |
198 | @@ -10,10 +10,10 @@ import sys |
199 | |
200 | from cloudinit.handlers.jinja_template import ( |
201 | convert_jinja_instance_data, render_jinja_payload) |
202 | -from cloudinit.cmd.devel import addLogHandlerCLI, read_cfg_paths |
203 | from cloudinit import log |
204 | from cloudinit.sources import ( |
205 | INSTANCE_JSON_FILE, INSTANCE_JSON_SENSITIVE_FILE, REDACT_SENSITIVE_VALUE) |
206 | +from cloudinit import stages |
207 | from cloudinit import util |
208 | |
209 | NAME = 'query' |
210 | @@ -69,7 +69,7 @@ def get_parser(parser=None): |
211 | def handle_args(name, args): |
212 | """Handle calls to 'cloud-init query' as a subcommand.""" |
213 | paths = None |
214 | - addLogHandlerCLI(LOG, log.DEBUG if args.debug else log.WARNING) |
215 | + log.addLogHandlerCLI(LOG, log.DEBUG if args.debug else log.WARNING) |
216 | if not any([args.list_keys, args.varname, args.format, args.dump_all]): |
217 | LOG.error( |
218 | 'Expected one of the options: --all, --format,' |
219 | @@ -79,7 +79,7 @@ def handle_args(name, args): |
220 | |
221 | uid = os.getuid() |
222 | if not all([args.instance_data, args.user_data, args.vendor_data]): |
223 | - paths = read_cfg_paths() |
224 | + paths = stages.read_cfg_paths() |
225 | if args.instance_data: |
226 | instance_data_fn = args.instance_data |
227 | else: |
228 | diff --git a/cloudinit/cmd/tests/test_clean.py b/cloudinit/cmd/tests/test_clean.py |
229 | index 5a3ec3b..266db32 100644 |
230 | --- a/cloudinit/cmd/tests/test_clean.py |
231 | +++ b/cloudinit/cmd/tests/test_clean.py |
232 | @@ -169,7 +169,7 @@ class TestClean(CiTestCase): |
233 | 'sys.argv': {'new': ['clean', '--logs']}}, |
234 | clean.main) |
235 | |
236 | - self.assertEqual(0, context_manager.exception.code) |
237 | + self.assertEqual('0', str(context_manager.exception)) |
238 | self.assertFalse( |
239 | os.path.exists(self.log1), 'Unexpected log {0}'.format(self.log1)) |
240 | |
241 | diff --git a/cloudinit/cmd/tests/test_cloud_id.py b/cloudinit/cmd/tests/test_cloud_id.py |
242 | index 7373817..3998272 100644 |
243 | --- a/cloudinit/cmd/tests/test_cloud_id.py |
244 | +++ b/cloudinit/cmd/tests/test_cloud_id.py |
245 | @@ -49,7 +49,7 @@ class TestCloudId(CiTestCase): |
246 | with mock.patch('sys.stderr', new_callable=StringIO) as m_stderr: |
247 | with self.assertRaises(SystemExit) as context_manager: |
248 | cloud_id.main() |
249 | - self.assertEqual(1, context_manager.exception.code) |
250 | + self.assertEqual('1', str(context_manager.exception)) |
251 | self.assertIn( |
252 | "ERROR: File not found '%s'" % self.instance_data, |
253 | m_stderr.getvalue()) |
254 | @@ -62,7 +62,7 @@ class TestCloudId(CiTestCase): |
255 | with mock.patch('sys.stderr', new_callable=StringIO) as m_stderr: |
256 | with self.assertRaises(SystemExit) as context_manager: |
257 | cloud_id.main() |
258 | - self.assertEqual(1, context_manager.exception.code) |
259 | + self.assertEqual('1', str(context_manager.exception)) |
260 | self.assertIn( |
261 | "ERROR: File '%s' is not valid json." % self.instance_data, |
262 | m_stderr.getvalue()) |
263 | @@ -77,7 +77,7 @@ class TestCloudId(CiTestCase): |
264 | with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout: |
265 | with self.assertRaises(SystemExit) as context_manager: |
266 | cloud_id.main() |
267 | - self.assertEqual(0, context_manager.exception.code) |
268 | + self.assertEqual('0', str(context_manager.exception)) |
269 | self.assertEqual("mycloud\n", m_stdout.getvalue()) |
270 | |
271 | def test_cloud_id_long_name_from_instance_data(self): |
272 | @@ -90,7 +90,7 @@ class TestCloudId(CiTestCase): |
273 | with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout: |
274 | with self.assertRaises(SystemExit) as context_manager: |
275 | cloud_id.main() |
276 | - self.assertEqual(0, context_manager.exception.code) |
277 | + self.assertEqual('0', str(context_manager.exception)) |
278 | self.assertEqual("mycloud\tsomereg\n", m_stdout.getvalue()) |
279 | |
280 | def test_cloud_id_lookup_from_instance_data_region(self): |
281 | @@ -104,7 +104,7 @@ class TestCloudId(CiTestCase): |
282 | with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout: |
283 | with self.assertRaises(SystemExit) as context_manager: |
284 | cloud_id.main() |
285 | - self.assertEqual(0, context_manager.exception.code) |
286 | + self.assertEqual('0', str(context_manager.exception)) |
287 | self.assertEqual("aws-china\tcn-north-1\n", m_stdout.getvalue()) |
288 | |
289 | def test_cloud_id_lookup_json_instance_data_adds_cloud_id_to_json(self): |
290 | @@ -121,7 +121,7 @@ class TestCloudId(CiTestCase): |
291 | with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout: |
292 | with self.assertRaises(SystemExit) as context_manager: |
293 | cloud_id.main() |
294 | - self.assertEqual(0, context_manager.exception.code) |
295 | + self.assertEqual('0', str(context_manager.exception)) |
296 | self.assertEqual(expected + '\n', m_stdout.getvalue()) |
297 | |
298 | # vi: ts=4 expandtab |
299 | diff --git a/cloudinit/cmd/tests/test_query.py b/cloudinit/cmd/tests/test_query.py |
300 | index 28738b1..3b32bd3 100644 |
301 | --- a/cloudinit/cmd/tests/test_query.py |
302 | +++ b/cloudinit/cmd/tests/test_query.py |
303 | @@ -27,21 +27,20 @@ class TestQuery(CiTestCase): |
304 | super(TestQuery, self).setUp() |
305 | self.tmp = self.tmp_dir() |
306 | self.instance_data = self.tmp_path('instance-data', dir=self.tmp) |
307 | + self.add_patch('cloudinit.log.addLogHandlerCLI', 'm_logcli') |
308 | |
309 | def test_handle_args_error_on_missing_param(self): |
310 | """Error when missing required parameters and print usage.""" |
311 | args = self.args( |
312 | debug=False, dump_all=False, format=None, instance_data=None, |
313 | list_keys=False, user_data=None, vendor_data=None, varname=None) |
314 | - with mock.patch('sys.stderr', new_callable=StringIO) as m_stderr: |
315 | - with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout: |
316 | - self.assertEqual(1, query.handle_args('anyname', args)) |
317 | + with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout: |
318 | + self.assertEqual(1, query.handle_args('anyname', args)) |
319 | expected_error = ( |
320 | 'ERROR: Expected one of the options: --all, --format, --list-keys' |
321 | ' or varname\n') |
322 | self.assertIn(expected_error, self.logs.getvalue()) |
323 | self.assertIn('usage: query', m_stdout.getvalue()) |
324 | - self.assertIn(expected_error, m_stderr.getvalue()) |
325 | |
326 | def test_handle_args_error_on_missing_instance_data(self): |
327 | """When instance_data file path does not exist, log an error.""" |
328 | @@ -49,14 +48,10 @@ class TestQuery(CiTestCase): |
329 | args = self.args( |
330 | debug=False, dump_all=True, format=None, instance_data=absent_fn, |
331 | list_keys=False, user_data='ud', vendor_data='vd', varname=None) |
332 | - with mock.patch('sys.stderr', new_callable=StringIO) as m_stderr: |
333 | - self.assertEqual(1, query.handle_args('anyname', args)) |
334 | + self.assertEqual(1, query.handle_args('anyname', args)) |
335 | self.assertIn( |
336 | 'ERROR: Missing instance-data file: %s' % absent_fn, |
337 | self.logs.getvalue()) |
338 | - self.assertIn( |
339 | - 'ERROR: Missing instance-data file: %s' % absent_fn, |
340 | - m_stderr.getvalue()) |
341 | |
342 | def test_handle_args_error_when_no_read_permission_instance_data(self): |
343 | """When instance_data file is unreadable, log an error.""" |
344 | @@ -65,16 +60,12 @@ class TestQuery(CiTestCase): |
345 | args = self.args( |
346 | debug=False, dump_all=True, format=None, instance_data=noread_fn, |
347 | list_keys=False, user_data='ud', vendor_data='vd', varname=None) |
348 | - with mock.patch('sys.stderr', new_callable=StringIO) as m_stderr: |
349 | - with mock.patch('cloudinit.cmd.query.util.load_file') as m_load: |
350 | - m_load.side_effect = OSError(errno.EACCES, 'Not allowed') |
351 | - self.assertEqual(1, query.handle_args('anyname', args)) |
352 | + with mock.patch('cloudinit.cmd.query.util.load_file') as m_load: |
353 | + m_load.side_effect = OSError(errno.EACCES, 'Not allowed') |
354 | + self.assertEqual(1, query.handle_args('anyname', args)) |
355 | self.assertIn( |
356 | "ERROR: No read permission on '%s'. Try sudo" % noread_fn, |
357 | self.logs.getvalue()) |
358 | - self.assertIn( |
359 | - "ERROR: No read permission on '%s'. Try sudo" % noread_fn, |
360 | - m_stderr.getvalue()) |
361 | |
362 | def test_handle_args_defaults_instance_data(self): |
363 | """When no instance_data argument, default to configured run_dir.""" |
364 | @@ -84,17 +75,13 @@ class TestQuery(CiTestCase): |
365 | run_dir = self.tmp_path('run_dir', dir=self.tmp) |
366 | ensure_dir(run_dir) |
367 | paths = Paths({'run_dir': run_dir}) |
368 | - self.add_patch('cloudinit.cmd.query.read_cfg_paths', 'm_paths') |
369 | + self.add_patch('cloudinit.stages.read_cfg_paths', 'm_paths') |
370 | self.m_paths.return_value = paths |
371 | - with mock.patch('sys.stderr', new_callable=StringIO) as m_stderr: |
372 | - self.assertEqual(1, query.handle_args('anyname', args)) |
373 | + self.assertEqual(1, query.handle_args('anyname', args)) |
374 | json_file = os.path.join(run_dir, INSTANCE_JSON_FILE) |
375 | self.assertIn( |
376 | 'ERROR: Missing instance-data file: %s' % json_file, |
377 | self.logs.getvalue()) |
378 | - self.assertIn( |
379 | - 'ERROR: Missing instance-data file: %s' % json_file, |
380 | - m_stderr.getvalue()) |
381 | |
382 | def test_handle_args_root_fallsback_to_instance_data(self): |
383 | """When no instance_data argument, root falls back to redacted json.""" |
384 | @@ -104,18 +91,17 @@ class TestQuery(CiTestCase): |
385 | run_dir = self.tmp_path('run_dir', dir=self.tmp) |
386 | ensure_dir(run_dir) |
387 | paths = Paths({'run_dir': run_dir}) |
388 | - self.add_patch('cloudinit.cmd.query.read_cfg_paths', 'm_paths') |
389 | + self.add_patch('cloudinit.stages.read_cfg_paths', 'm_paths') |
390 | self.m_paths.return_value = paths |
391 | - with mock.patch('sys.stderr', new_callable=StringIO) as m_stderr: |
392 | - with mock.patch('os.getuid') as m_getuid: |
393 | - m_getuid.return_value = 0 |
394 | - self.assertEqual(1, query.handle_args('anyname', args)) |
395 | + with mock.patch('os.getuid') as m_getuid: |
396 | + m_getuid.return_value = 0 |
397 | + self.assertEqual(1, query.handle_args('anyname', args)) |
398 | json_file = os.path.join(run_dir, INSTANCE_JSON_FILE) |
399 | sensitive_file = os.path.join(run_dir, INSTANCE_JSON_SENSITIVE_FILE) |
400 | self.assertIn( |
401 | 'WARNING: Missing root-readable %s. Using redacted %s instead.' % ( |
402 | sensitive_file, json_file), |
403 | - m_stderr.getvalue()) |
404 | + self.logs.getvalue()) |
405 | |
406 | def test_handle_args_root_uses_instance_sensitive_data(self): |
407 | """When no instance_data argument, root uses semsitive json.""" |
408 | @@ -128,7 +114,7 @@ class TestQuery(CiTestCase): |
409 | write_file(sensitive_file, '{"my-var": "it worked"}') |
410 | ensure_dir(run_dir) |
411 | paths = Paths({'run_dir': run_dir}) |
412 | - self.add_patch('cloudinit.cmd.query.read_cfg_paths', 'm_paths') |
413 | + self.add_patch('cloudinit.stages.read_cfg_paths', 'm_paths') |
414 | self.m_paths.return_value = paths |
415 | args = self.args( |
416 | debug=False, dump_all=True, format=None, instance_data=None, |
417 | @@ -150,7 +136,9 @@ class TestQuery(CiTestCase): |
418 | instance_data=self.instance_data, list_keys=False, |
419 | user_data='ud', vendor_data='vd', varname=None) |
420 | with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout: |
421 | - self.assertEqual(0, query.handle_args('anyname', args)) |
422 | + with mock.patch('os.getuid') as m_getuid: |
423 | + m_getuid.return_value = 100 |
424 | + self.assertEqual(0, query.handle_args('anyname', args)) |
425 | self.assertEqual( |
426 | '{\n "my_var": "it worked",\n "userdata": "<%s> file:ud",\n' |
427 | ' "vendordata": "<%s> file:vd"\n}\n' % ( |
428 | @@ -177,7 +165,9 @@ class TestQuery(CiTestCase): |
429 | instance_data=self.instance_data, user_data='ud', vendor_data='vd', |
430 | list_keys=False, varname='v1.key_2') |
431 | with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout: |
432 | - self.assertEqual(0, query.handle_args('anyname', args)) |
433 | + with mock.patch('os.getuid') as m_getuid: |
434 | + m_getuid.return_value = 100 |
435 | + self.assertEqual(0, query.handle_args('anyname', args)) |
436 | self.assertEqual('value-2\n', m_stdout.getvalue()) |
437 | |
438 | def test_handle_args_returns_standardized_vars_to_top_level_aliases(self): |
439 | @@ -206,7 +196,9 @@ class TestQuery(CiTestCase): |
440 | instance_data=self.instance_data, user_data='ud', vendor_data='vd', |
441 | list_keys=False, varname=None) |
442 | with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout: |
443 | - self.assertEqual(0, query.handle_args('anyname', args)) |
444 | + with mock.patch('os.getuid') as m_getuid: |
445 | + m_getuid.return_value = 100 |
446 | + self.assertEqual(0, query.handle_args('anyname', args)) |
447 | self.assertEqual(expected, m_stdout.getvalue()) |
448 | |
449 | def test_handle_args_list_keys_sorts_top_level_keys_when_no_varname(self): |
450 | @@ -221,7 +213,9 @@ class TestQuery(CiTestCase): |
451 | instance_data=self.instance_data, list_keys=True, user_data='ud', |
452 | vendor_data='vd', varname=None) |
453 | with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout: |
454 | - self.assertEqual(0, query.handle_args('anyname', args)) |
455 | + with mock.patch('os.getuid') as m_getuid: |
456 | + m_getuid.return_value = 100 |
457 | + self.assertEqual(0, query.handle_args('anyname', args)) |
458 | self.assertEqual(expected, m_stdout.getvalue()) |
459 | |
460 | def test_handle_args_list_keys_sorts_nested_keys_when_varname(self): |
461 | @@ -236,7 +230,9 @@ class TestQuery(CiTestCase): |
462 | instance_data=self.instance_data, list_keys=True, |
463 | user_data='ud', vendor_data='vd', varname='v1') |
464 | with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout: |
465 | - self.assertEqual(0, query.handle_args('anyname', args)) |
466 | + with mock.patch('os.getuid') as m_getuid: |
467 | + m_getuid.return_value = 100 |
468 | + self.assertEqual(0, query.handle_args('anyname', args)) |
469 | self.assertEqual(expected, m_stdout.getvalue()) |
470 | |
471 | def test_handle_args_list_keys_errors_when_varname_is_not_a_dict(self): |
472 | @@ -250,10 +246,11 @@ class TestQuery(CiTestCase): |
473 | debug=False, dump_all=False, format=None, |
474 | instance_data=self.instance_data, list_keys=True, user_data='ud', |
475 | vendor_data='vd', varname='top') |
476 | - with mock.patch('sys.stderr', new_callable=StringIO) as m_stderr: |
477 | - with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout: |
478 | + with mock.patch('sys.stdout', new_callable=StringIO) as m_stdout: |
479 | + with mock.patch('os.getuid') as m_getuid: |
480 | + m_getuid.return_value = 100 |
481 | self.assertEqual(1, query.handle_args('anyname', args)) |
482 | self.assertEqual('', m_stdout.getvalue()) |
483 | - self.assertIn(expected_error, m_stderr.getvalue()) |
484 | + self.assertIn(expected_error, self.logs.getvalue()) |
485 | |
486 | # vi: ts=4 expandtab |
487 | diff --git a/cloudinit/cmd/tests/test_status.py b/cloudinit/cmd/tests/test_status.py |
488 | index aded858..9f742df 100644 |
489 | --- a/cloudinit/cmd/tests/test_status.py |
490 | +++ b/cloudinit/cmd/tests/test_status.py |
491 | @@ -386,7 +386,7 @@ class TestStatus(CiTestCase): |
492 | '_is_cloudinit_disabled': (False, ''), |
493 | 'Init': {'side_effect': self.init_class}}, |
494 | status.main) |
495 | - self.assertEqual(0, context_manager.exception.code) |
496 | + self.assertEqual('0', str(context_manager.exception)) |
497 | self.assertEqual('status: running\n', m_stdout.getvalue()) |
498 | |
499 | # vi: ts=4 expandtab syntax=python |
500 | diff --git a/cloudinit/helpers.py b/cloudinit/helpers.py |
501 | index dcd2645..22ecacd 100644 |
502 | --- a/cloudinit/helpers.py |
503 | +++ b/cloudinit/helpers.py |
504 | @@ -457,4 +457,11 @@ class DefaultingConfigParser(RawConfigParser): |
505 | def identity(object): |
506 | return object |
507 | |
508 | + |
509 | +def implement_unicode_from_str(object): |
510 | + """Add __unicode__ method to object from __str__ behavior.""" |
511 | + object.__unicode__ = object.__str__ |
512 | + object.__str__ = lambda x: x.__unicode().encode('utf-8') |
513 | + return object |
514 | + |
515 | # vi: ts=4 expandtab |
516 | diff --git a/cloudinit/log.py b/cloudinit/log.py |
517 | index 5ae312b..f63190a 100644 |
518 | --- a/cloudinit/log.py |
519 | +++ b/cloudinit/log.py |
520 | @@ -126,6 +126,13 @@ def getLogger(name='cloudinit'): |
521 | return logging.getLogger(name) |
522 | |
523 | |
524 | +def addLogHandlerCLI(logger, log_level): |
525 | + """Add a commandline logging handler to emit messages to stderr.""" |
526 | + formatter = logging.Formatter('%(levelname)s: %(message)s') |
527 | + setupBasicLogging(log_level, formatter=formatter) |
528 | + return logger |
529 | + |
530 | + |
531 | # Fixes this annoyance... |
532 | # No handlers could be found for logger XXX annoying output... |
533 | try: |
534 | diff --git a/cloudinit/sources/helpers/netlink.py b/cloudinit/sources/helpers/netlink.py |
535 | index d377ae3..89b4e6e 100644 |
536 | --- a/cloudinit/sources/helpers/netlink.py |
537 | +++ b/cloudinit/sources/helpers/netlink.py |
538 | @@ -97,6 +97,8 @@ def get_netlink_msg_header(data): |
539 | assert (data is not None), ("data is none") |
540 | assert (len(data) >= NLMSGHDR_SIZE), ( |
541 | "data is smaller than netlink message header") |
542 | + if util.PY26: |
543 | + data = str(data) |
544 | msg_len, msg_type, flags, seq, pid = struct.unpack(NLMSGHDR_FMT, |
545 | data[:MSG_TYPE_OFFSET]) |
546 | LOG.debug("Got netlink msg of type %d", msg_type) |
547 | @@ -140,6 +142,8 @@ def unpack_rta_attr(data, offset): |
548 | "rta offset is less than expected length") |
549 | length = rta_type = 0 |
550 | attr_data = None |
551 | + if util.PY26: |
552 | + data = str(data) |
553 | try: |
554 | length = struct.unpack_from("H", data, offset=offset)[0] |
555 | rta_type = struct.unpack_from("H", data, offset=offset+2)[0] |
556 | diff --git a/cloudinit/stages.py b/cloudinit/stages.py |
557 | index 8a06412..badc883 100644 |
558 | --- a/cloudinit/stages.py |
559 | +++ b/cloudinit/stages.py |
560 | @@ -889,6 +889,13 @@ def fetch_base_config(): |
561 | ], reverse=True) |
562 | |
563 | |
564 | +def read_cfg_paths(): |
565 | + """Return a Paths object based on the system configuration on disk.""" |
566 | + init = Init(ds_deps=[]) |
567 | + init.read_cfg() |
568 | + return init.paths |
569 | + |
570 | + |
571 | def _pkl_store(obj, fname): |
572 | try: |
573 | pk_contents = pickle.dumps(obj) |
574 | diff --git a/cloudinit/templater.py b/cloudinit/templater.py |
575 | index b668674..85a5b0f 100644 |
576 | --- a/cloudinit/templater.py |
577 | +++ b/cloudinit/templater.py |
578 | @@ -13,6 +13,9 @@ |
579 | import collections |
580 | import re |
581 | |
582 | +from cloudinit import log as logging |
583 | +from cloudinit import type_utils as tu |
584 | +from cloudinit import util |
585 | |
586 | try: |
587 | from Cheetah.Template import Template as CTemplate |
588 | @@ -21,19 +24,24 @@ except (ImportError, AttributeError): |
589 | CHEETAH_AVAILABLE = False |
590 | |
591 | try: |
592 | - from jinja2.runtime import implements_to_string |
593 | from jinja2 import Template as JTemplate |
594 | from jinja2 import DebugUndefined as JUndefined |
595 | JINJA_AVAILABLE = True |
596 | except (ImportError, AttributeError): |
597 | - from cloudinit.helpers import identity |
598 | - implements_to_string = identity |
599 | JINJA_AVAILABLE = False |
600 | JUndefined = object |
601 | |
602 | -from cloudinit import log as logging |
603 | -from cloudinit import type_utils as tu |
604 | -from cloudinit import util |
605 | +if JINJA_AVAILABLE: |
606 | + try: |
607 | + # centos 6 jinja2 (2.2.1) works other than missing implements_to_string |
608 | + from jinja2.runtime import implements_to_string |
609 | + except ImportError: |
610 | + if not hasattr(JUndefined, '__unicode__'): |
611 | + from cloudinit.helpers import identity |
612 | + implements_to_string = identity |
613 | + else: |
614 | + from cloudinit.helpers import implement_unicode_from_str |
615 | + implements_to_string = implement_unicode_from_str |
616 | |
617 | |
618 | LOG = logging.getLogger(__name__) |
619 | @@ -47,7 +55,11 @@ class UndefinedJinjaVariable(JUndefined): |
620 | """Class used to represent any undefined jinja template varible.""" |
621 | |
622 | def __str__(self): |
623 | - return u'%s%s' % (MISSING_JINJA_PREFIX, self._undefined_name) |
624 | + if hasattr(self, '_undefined_name'): |
625 | + hint = getattr(self, '_undefined_name') |
626 | + else: |
627 | + hint = getattr(self, '_undefined_hint') |
628 | + return u'%s%s' % (MISSING_JINJA_PREFIX, hint) |
629 | |
630 | def __sub__(self, other): |
631 | other = str(other).replace(MISSING_JINJA_PREFIX, '') |
632 | diff --git a/tests/unittests/test_builtin_handlers.py b/tests/unittests/test_builtin_handlers.py |
633 | index b92ffc7..b95de01 100644 |
634 | --- a/tests/unittests/test_builtin_handlers.py |
635 | +++ b/tests/unittests/test_builtin_handlers.py |
636 | @@ -197,7 +197,7 @@ class TestJinjaTemplatePartHandler(CiTestCase): |
637 | script_file = os.path.join(script_handler.script_dir, 'part01') |
638 | self.assertEqual( |
639 | 'Cannot render jinja template vars. Instance data not yet present' |
640 | - ' at {}/instance-data.json'.format( |
641 | + ' at {0}/instance-data.json'.format( |
642 | self.run_dir), str(context_manager.exception)) |
643 | self.assertFalse( |
644 | os.path.exists(script_file), |
645 | @@ -246,7 +246,7 @@ class TestJinjaTemplatePartHandler(CiTestCase): |
646 | frequency='freq', headers='headers') |
647 | script_file = os.path.join(script_handler.script_dir, 'part01') |
648 | self.assertNotIn( |
649 | - 'Instance data not yet present at {}/instance-data.json'.format( |
650 | + 'Instance data not yet present at {0}/instance-data.json'.format( |
651 | self.run_dir), |
652 | self.logs.getvalue()) |
653 | self.assertEqual( |
654 | diff --git a/tests/unittests/test_handler/test_schema.py b/tests/unittests/test_handler/test_schema.py |
655 | index 1bad07f..c8e5968 100644 |
656 | --- a/tests/unittests/test_handler/test_schema.py |
657 | +++ b/tests/unittests/test_handler/test_schema.py |
658 | @@ -350,7 +350,7 @@ class MainTest(CiTestCase): |
659 | m_stderr: |
660 | with self.assertRaises(SystemExit) as context_manager: |
661 | main() |
662 | - self.assertEqual(1, context_manager.exception.code) |
663 | + self.assertEqual('1', str(context_manager.exception)) |
664 | self.assertEqual( |
665 | 'Expected either --config-file argument or --doc\n', |
666 | m_stderr.getvalue()) |
667 | @@ -364,7 +364,7 @@ class MainTest(CiTestCase): |
668 | m_stderr: |
669 | with self.assertRaises(SystemExit) as context_manager: |
670 | main() |
671 | - self.assertEqual(1, context_manager.exception.code) |
672 | + self.assertEqual('1', str(context_manager.exception)) |
673 | self.assertEqual( |
674 | 'Configfile NOT_A_FILE does not exist\n', |
675 | m_stderr.getvalue()) |
PASSED: Continuous integration, rev:b41878cb809 97dafa51fd2f4c7 3d74bf82387f24 /jenkins. ubuntu. com/server/ job/cloud- init-ci/ 487/
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: /jenkins. ubuntu. com/server/ job/cloud- init-ci/ 487/rebuild
https:/