Merge ~chad.smith/cloud-init:cleanup/report-unreadable-instance-data into cloud-init:master

Proposed by Chad Smith
Status: Merged
Approved by: Chad Smith
Approved revision: 32a1a647b1b5a3588965e18e84f6dca61f9e7683
Merge reported by: Server Team CI bot
Merged at revision: not available
Proposed branch: ~chad.smith/cloud-init:cleanup/report-unreadable-instance-data
Merge into: cloud-init:master
Diff against target: 187 lines (+72/-12)
6 files modified
cloudinit/cmd/devel/render.py (+8/-4)
cloudinit/cmd/query.py (+6/-2)
cloudinit/cmd/tests/test_query.py (+23/-4)
cloudinit/handlers/jinja_template.py (+9/-1)
doc/rtd/topics/network-config-format-v1.rst (+1/-1)
tests/unittests/test_builtin_handlers.py (+25/-0)
Reviewer Review Type Date Requested Status
Server Team CI bot continuous-integration Approve
cloud-init Commiters Pending
Review via email: mp+358041@code.launchpad.net

Commit message

query: better error when missing read permission on instance-data

Emit a permissions error instead of "Missing instance-data.json" when
non-root user doesn't have read-permission on
/run/cloud-init/instance-data.json

To post a comment you must log in.
Revision history for this message
Scott Moser (smoser) wrote :

seems sane. one request.

32a1a64... by Chad Smith

single quotes around instance-data filename

Revision history for this message
Server Team CI bot (server-team-bot) wrote :

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

review: Approve (continuous-integration)
Revision history for this message
Server Team CI bot (server-team-bot) wrote :

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

review: Approve (continuous-integration)
Revision history for this message
Chad Smith (chad.smith) :

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/cloudinit/cmd/devel/render.py b/cloudinit/cmd/devel/render.py
2index 4d3ec95..1bc2240 100755
3--- a/cloudinit/cmd/devel/render.py
4+++ b/cloudinit/cmd/devel/render.py
5@@ -71,10 +71,14 @@ def handle_args(name, args):
6 except IOError:
7 LOG.error('Missing user-data file: %s', args.user_data)
8 return 1
9- rendered_payload = render_jinja_payload_from_file(
10- payload=user_data, payload_fn=args.user_data,
11- instance_data_file=instance_data_fn,
12- debug=True if args.debug else False)
13+ try:
14+ rendered_payload = render_jinja_payload_from_file(
15+ payload=user_data, payload_fn=args.user_data,
16+ instance_data_file=instance_data_fn,
17+ debug=True if args.debug else False)
18+ except RuntimeError as e:
19+ LOG.error('Cannot render from instance data: %s', str(e))
20+ return 1
21 if not rendered_payload:
22 LOG.error('Unable to render user-data file: %s', args.user_data)
23 return 1
24diff --git a/cloudinit/cmd/query.py b/cloudinit/cmd/query.py
25index ff03de9..1d888b9 100644
26--- a/cloudinit/cmd/query.py
27+++ b/cloudinit/cmd/query.py
28@@ -3,6 +3,7 @@
29 """Query standardized instance metadata from the command line."""
30
31 import argparse
32+from errno import EACCES
33 import os
34 import six
35 import sys
36@@ -106,8 +107,11 @@ def handle_args(name, args):
37
38 try:
39 instance_json = util.load_file(instance_data_fn)
40- except IOError:
41- LOG.error('Missing instance-data.json file: %s', instance_data_fn)
42+ except (IOError, OSError) as e:
43+ if e.errno == EACCES:
44+ LOG.error("No read permission on '%s'. Try sudo", instance_data_fn)
45+ else:
46+ LOG.error('Missing instance-data file: %s', instance_data_fn)
47 return 1
48
49 instance_data = util.load_json(instance_json)
50diff --git a/cloudinit/cmd/tests/test_query.py b/cloudinit/cmd/tests/test_query.py
51index 241f541..28738b1 100644
52--- a/cloudinit/cmd/tests/test_query.py
53+++ b/cloudinit/cmd/tests/test_query.py
54@@ -1,5 +1,6 @@
55 # This file is part of cloud-init. See LICENSE file for license information.
56
57+import errno
58 from six import StringIO
59 from textwrap import dedent
60 import os
61@@ -51,10 +52,28 @@ class TestQuery(CiTestCase):
62 with mock.patch('sys.stderr', new_callable=StringIO) as m_stderr:
63 self.assertEqual(1, query.handle_args('anyname', args))
64 self.assertIn(
65- 'ERROR: Missing instance-data.json file: %s' % absent_fn,
66+ 'ERROR: Missing instance-data file: %s' % absent_fn,
67 self.logs.getvalue())
68 self.assertIn(
69- 'ERROR: Missing instance-data.json file: %s' % absent_fn,
70+ 'ERROR: Missing instance-data file: %s' % absent_fn,
71+ m_stderr.getvalue())
72+
73+ def test_handle_args_error_when_no_read_permission_instance_data(self):
74+ """When instance_data file is unreadable, log an error."""
75+ noread_fn = self.tmp_path('unreadable', dir=self.tmp)
76+ write_file(noread_fn, 'thou shall not pass')
77+ args = self.args(
78+ debug=False, dump_all=True, format=None, instance_data=noread_fn,
79+ list_keys=False, user_data='ud', vendor_data='vd', varname=None)
80+ with mock.patch('sys.stderr', new_callable=StringIO) as m_stderr:
81+ with mock.patch('cloudinit.cmd.query.util.load_file') as m_load:
82+ m_load.side_effect = OSError(errno.EACCES, 'Not allowed')
83+ self.assertEqual(1, query.handle_args('anyname', args))
84+ self.assertIn(
85+ "ERROR: No read permission on '%s'. Try sudo" % noread_fn,
86+ self.logs.getvalue())
87+ self.assertIn(
88+ "ERROR: No read permission on '%s'. Try sudo" % noread_fn,
89 m_stderr.getvalue())
90
91 def test_handle_args_defaults_instance_data(self):
92@@ -71,10 +90,10 @@ class TestQuery(CiTestCase):
93 self.assertEqual(1, query.handle_args('anyname', args))
94 json_file = os.path.join(run_dir, INSTANCE_JSON_FILE)
95 self.assertIn(
96- 'ERROR: Missing instance-data.json file: %s' % json_file,
97+ 'ERROR: Missing instance-data file: %s' % json_file,
98 self.logs.getvalue())
99 self.assertIn(
100- 'ERROR: Missing instance-data.json file: %s' % json_file,
101+ 'ERROR: Missing instance-data file: %s' % json_file,
102 m_stderr.getvalue())
103
104 def test_handle_args_root_fallsback_to_instance_data(self):
105diff --git a/cloudinit/handlers/jinja_template.py b/cloudinit/handlers/jinja_template.py
106index 3fa4097..ce3accf 100644
107--- a/cloudinit/handlers/jinja_template.py
108+++ b/cloudinit/handlers/jinja_template.py
109@@ -1,5 +1,6 @@
110 # This file is part of cloud-init. See LICENSE file for license information.
111
112+from errno import EACCES
113 import os
114 import re
115
116@@ -76,7 +77,14 @@ def render_jinja_payload_from_file(
117 raise RuntimeError(
118 'Cannot render jinja template vars. Instance data not yet'
119 ' present at %s' % instance_data_file)
120- instance_data = load_json(load_file(instance_data_file))
121+ try:
122+ instance_data = load_json(load_file(instance_data_file))
123+ except (IOError, OSError) as e:
124+ if e.errno == EACCES:
125+ raise RuntimeError(
126+ 'Cannot render jinja template vars. No read permission on'
127+ " '%s'. Try sudo" % instance_data_file)
128+
129 rendered_payload = render_jinja_payload(
130 payload, payload_fn, instance_data, debug)
131 if not rendered_payload:
132diff --git a/doc/rtd/topics/network-config-format-v1.rst b/doc/rtd/topics/network-config-format-v1.rst
133index 8352000..3b0148c 100644
134--- a/doc/rtd/topics/network-config-format-v1.rst
135+++ b/doc/rtd/topics/network-config-format-v1.rst
136@@ -332,7 +332,7 @@ the following keys:
137 - type: static
138 address: 192.168.23.14/27
139 gateway: 192.168.23.1
140- - type: nameserver
141+ - type: nameserver:
142 address:
143 - 192.168.23.2
144 - 8.8.8.8
145diff --git a/tests/unittests/test_builtin_handlers.py b/tests/unittests/test_builtin_handlers.py
146index abe820e..b92ffc7 100644
147--- a/tests/unittests/test_builtin_handlers.py
148+++ b/tests/unittests/test_builtin_handlers.py
149@@ -3,6 +3,7 @@
150 """Tests of the built-in user data handlers."""
151
152 import copy
153+import errno
154 import os
155 import shutil
156 import tempfile
157@@ -202,6 +203,30 @@ class TestJinjaTemplatePartHandler(CiTestCase):
158 os.path.exists(script_file),
159 'Unexpected file created %s' % script_file)
160
161+ def test_jinja_template_handle_errors_on_unreadable_instance_data(self):
162+ """If instance-data is unreadable, raise an error from handle_part."""
163+ script_handler = ShellScriptPartHandler(self.paths)
164+ instance_json = os.path.join(self.run_dir, 'instance-data.json')
165+ util.write_file(instance_json, util.json_dumps({}))
166+ h = JinjaTemplatePartHandler(
167+ self.paths, sub_handlers=[script_handler])
168+ with mock.patch(self.mpath + 'load_file') as m_load:
169+ with self.assertRaises(RuntimeError) as context_manager:
170+ m_load.side_effect = OSError(errno.EACCES, 'Not allowed')
171+ h.handle_part(
172+ data='data', ctype="!" + handlers.CONTENT_START,
173+ filename='part01',
174+ payload='## template: jinja \n#!/bin/bash\necho himom',
175+ frequency='freq', headers='headers')
176+ script_file = os.path.join(script_handler.script_dir, 'part01')
177+ self.assertEqual(
178+ 'Cannot render jinja template vars. No read permission on'
179+ " '{rdir}/instance-data.json'. Try sudo".format(rdir=self.run_dir),
180+ str(context_manager.exception))
181+ self.assertFalse(
182+ os.path.exists(script_file),
183+ 'Unexpected file created %s' % script_file)
184+
185 @skipUnlessJinja()
186 def test_jinja_template_handle_renders_jinja_content(self):
187 """When present, render jinja variables from instance-data.json."""

Subscribers

People subscribed via source and target branches