Merge ~chad.smith/cloud-init:fix/cent-6-jinja2 into cloud-init:master

Proposed by Chad Smith
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)
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.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.

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/run-container --package --source-package --unittest \
 --artifacts=./rpm/ centos/6

LP: #1795933

Description of the change

see commit message

To post a comment you must log in.
Revision history for this message
Server Team CI bot (server-team-bot) wrote :

PASSED: Continuous integration, rev:b41878cb80997dafa51fd2f4c73d74bf82387f24
https://jenkins.ubuntu.com/server/job/cloud-init-ci/487/
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://jenkins.ubuntu.com/server/job/cloud-init-ci/487/rebuild

review: Approve (continuous-integration)
Revision history for this message
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.

Revision history for this message
Ryan Harper (raharper) wrote :

I'd like to see this rebased against 18.3 since that's the last PY26 supported release.

review: Needs Fixing

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.addLogHandlerCLI -> 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

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/cloudinit/cmd/devel/__init__.py b/cloudinit/cmd/devel/__init__.py
2index 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
31diff --git a/cloudinit/cmd/devel/render.py b/cloudinit/cmd/devel/render.py
32index 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:
58diff --git a/cloudinit/cmd/devel/tests/test_render.py b/cloudinit/cmd/devel/tests/test_render.py
59index 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
194diff --git a/cloudinit/cmd/query.py b/cloudinit/cmd/query.py
195index 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:
228diff --git a/cloudinit/cmd/tests/test_clean.py b/cloudinit/cmd/tests/test_clean.py
229index 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
241diff --git a/cloudinit/cmd/tests/test_cloud_id.py b/cloudinit/cmd/tests/test_cloud_id.py
242index 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
299diff --git a/cloudinit/cmd/tests/test_query.py b/cloudinit/cmd/tests/test_query.py
300index 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
487diff --git a/cloudinit/cmd/tests/test_status.py b/cloudinit/cmd/tests/test_status.py
488index 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
500diff --git a/cloudinit/helpers.py b/cloudinit/helpers.py
501index 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
516diff --git a/cloudinit/log.py b/cloudinit/log.py
517index 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:
534diff --git a/cloudinit/sources/helpers/netlink.py b/cloudinit/sources/helpers/netlink.py
535index 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]
556diff --git a/cloudinit/stages.py b/cloudinit/stages.py
557index 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)
574diff --git a/cloudinit/templater.py b/cloudinit/templater.py
575index 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, '')
632diff --git a/tests/unittests/test_builtin_handlers.py b/tests/unittests/test_builtin_handlers.py
633index 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(
654diff --git a/tests/unittests/test_handler/test_schema.py b/tests/unittests/test_handler/test_schema.py
655index 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())

Subscribers

People subscribed via source and target branches