Merge ~ajorgens/cloud-init:octal-modes into cloud-init:master

Proposed by Andrew Jorgensen
Status: Merged
Approved by: Scott Moser
Approved revision: 2f7132a0fa8ae12f9771b5e3c44f93ef1685ea75
Merged at revision: 0fe6a0607408d387f4b0d4482b95afbc5d3f3909
Proposed branch: ~ajorgens/cloud-init:octal-modes
Merge into: cloud-init:master
Diff against target: 146 lines (+52/-9)
6 files modified
cloudinit/config/cc_write_files.py (+9/-1)
cloudinit/util.py (+5/-1)
tests/unittests/helpers.py (+5/-3)
tests/unittests/test_datasource/test_azure.py (+2/-1)
tests/unittests/test_handler/test_handler_ntp.py (+2/-1)
tests/unittests/test_handler/test_handler_write_files.py (+29/-2)
Reviewer Review Type Date Requested Status
Scott Moser Approve
Server Team CI bot continuous-integration Approve
Chad Smith Approve
Andrew Jorgensen (community) Needs Resubmitting
Review via email: mp+325424@code.launchpad.net

Commit message

write_file(s): Print file modes as octal, not decimal

Unix file modes are usually represented as octal, but they were being
interpreted as decimal, for example 0o644 would be printed as '420'.

To post a comment you must log in.
Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Andrew Jorgensen (ajorgens) wrote :

Well, dang, I thought this was going to be the easy one to get merged. Why is the mode None? *sigh*

Revision history for this message
Scott Moser (smoser) wrote :

Andrew,

Thanks for re-submitting this MP.

I've reviewed a little bit and found a few things
a.) as seen in the c-i logs, the change to 'decode_perms' can fail if the input was a string.
  >>> "%o" % "0755"
  TypeError: %o format: an integer is required, not str

b.) it seems to have exposed an error in append_file() that i'm not sure how it ever worked.

For your MP to be accepted, we need to fix 'a'

I've put a suggested change like:
 http://paste.ubuntu.com/24841624/

Basically that just tries to do '%o' but uses '%r' if that fails.

Thoughts?

review: Needs Fixing
~ajorgens/cloud-init:octal-modes updated
5a9acd1... by Andrew Jorgensen

Additional fixes and test case from smoser@

Revision history for this message
Andrew Jorgensen (ajorgens) wrote :

Both a and b are fixed in 5a9acd1

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

Thanks, this looks good to me.
I'm asking Chad for a quick review also.

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

Looks good and thanks for the unit tests. Since we now have a with_logs option in CiTest class, let's use that instead of mocks.

Here's a slight extension to our unit test logging setup to also make sure we check log level on some of these logs.
http://paste.ubuntu.com/24843185/

Approved if folks think this is a good idea with the following patch.

review: Approve
~ajorgens/cloud-init:octal-modes updated
2f7132a... by Andrew Jorgensen

Unit test fixes from csmith and smoser

Revision history for this message
Andrew Jorgensen (ajorg) wrote :

Ready to merge AFAICT.

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

I approve this.
We really ought to change the signature of decode_perms to not take a 'log' input but rather just to use logging.
Andrew, interested in doing a follow up for that?

review: Approve
Revision history for this message
Andrew Jorgensen (ajorgens) wrote :

> Andrew, interested in doing a follow up for that?

Probably. I've got a rather long queue of patches to post, but I've added this to the TODO list.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/cloudinit/config/cc_write_files.py b/cloudinit/config/cc_write_files.py
2index 72e1cdd..1835a31 100644
3--- a/cloudinit/config/cc_write_files.py
4+++ b/cloudinit/config/cc_write_files.py
5@@ -53,6 +53,7 @@ import six
6 from cloudinit.settings import PER_INSTANCE
7 from cloudinit import util
8
9+
10 frequency = PER_INSTANCE
11
12 DEFAULT_OWNER = "root:root"
13@@ -119,7 +120,14 @@ def decode_perms(perm, default, log):
14 # Force to string and try octal conversion
15 return int(str(perm), 8)
16 except (TypeError, ValueError):
17- log.warn("Undecodable permissions %s, assuming %s", perm, default)
18+ reps = []
19+ for r in (perm, default):
20+ try:
21+ reps.append("%o" % r)
22+ except TypeError:
23+ reps.append("%r" % r)
24+ log.warning(
25+ "Undecodable permissions {0}, returning default {1}".format(*reps))
26 return default
27
28
29diff --git a/cloudinit/util.py b/cloudinit/util.py
30index 415ca37..ec68925 100644
31--- a/cloudinit/util.py
32+++ b/cloudinit/util.py
33@@ -1751,8 +1751,12 @@ def write_file(filename, content, mode=0o644, omode="wb", copy_mode=False):
34 else:
35 content = decode_binary(content)
36 write_type = 'characters'
37+ try:
38+ mode_r = "%o" % mode
39+ except TypeError:
40+ mode_r = "%r" % mode
41 LOG.debug("Writing to %s - %s: [%s] %s %s",
42- filename, omode, mode, len(content), write_type)
43+ filename, omode, mode_r, len(content), write_type)
44 with SeLinuxGuard(path=filename):
45 with open(filename, omode) as fh:
46 fh.write(content)
47diff --git a/tests/unittests/helpers.py b/tests/unittests/helpers.py
48index e78abce..569f1ae 100644
49--- a/tests/unittests/helpers.py
50+++ b/tests/unittests/helpers.py
51@@ -97,11 +97,13 @@ class CiTestCase(TestCase):
52 super(CiTestCase, self).setUp()
53 if self.with_logs:
54 # Create a log handler so unit tests can search expected logs.
55- logger = logging.getLogger()
56+ self.logger = logging.getLogger()
57 self.logs = six.StringIO()
58+ formatter = logging.Formatter('%(levelname)s: %(message)s')
59 handler = logging.StreamHandler(self.logs)
60- self.old_handlers = logger.handlers
61- logger.handlers = [handler]
62+ handler.setFormatter(formatter)
63+ self.old_handlers = self.logger.handlers
64+ self.logger.handlers = [handler]
65
66 def tearDown(self):
67 if self.with_logs:
68diff --git a/tests/unittests/test_datasource/test_azure.py b/tests/unittests/test_datasource/test_azure.py
69index 42f49e0..b17f389 100644
70--- a/tests/unittests/test_datasource/test_azure.py
71+++ b/tests/unittests/test_datasource/test_azure.py
72@@ -263,7 +263,8 @@ fdescfs /dev/fd fdescfs rw 0 0
73 {}, distro=None, paths=self.paths)
74 self.assertFalse(dsrc.get_data())
75 self.assertEqual(
76- "Non-Azure DMI asset tag '{0}' discovered.\n".format(nonazure_tag),
77+ "DEBUG: Non-Azure DMI asset tag '{0}' discovered.\n".format(
78+ nonazure_tag),
79 self.logs.getvalue())
80
81 def test_basic_seed_dir(self):
82diff --git a/tests/unittests/test_handler/test_handler_ntp.py b/tests/unittests/test_handler/test_handler_ntp.py
83index 3a9f7f7..c4299d9 100644
84--- a/tests/unittests/test_handler/test_handler_ntp.py
85+++ b/tests/unittests/test_handler/test_handler_ntp.py
86@@ -216,7 +216,8 @@ class TestNtp(FilesystemMockingTestCase):
87 """When no ntp section is defined handler logs a warning and noops."""
88 cc_ntp.handle('cc_ntp', {}, None, None, [])
89 self.assertEqual(
90- 'Skipping module named cc_ntp, not present or disabled by cfg\n',
91+ 'DEBUG: Skipping module named cc_ntp, '
92+ 'not present or disabled by cfg\n',
93 self.logs.getvalue())
94
95 def test_ntp_handler_schema_validation_allows_empty_ntp_config(self):
96diff --git a/tests/unittests/test_handler/test_handler_write_files.py b/tests/unittests/test_handler/test_handler_write_files.py
97index fb252d1..88a4742 100644
98--- a/tests/unittests/test_handler/test_handler_write_files.py
99+++ b/tests/unittests/test_handler/test_handler_write_files.py
100@@ -1,10 +1,10 @@
101 # This file is part of cloud-init. See LICENSE file for license information.
102
103-from cloudinit.config.cc_write_files import write_files
104+from cloudinit.config.cc_write_files import write_files, decode_perms
105 from cloudinit import log as logging
106 from cloudinit import util
107
108-from ..helpers import FilesystemMockingTestCase
109+from ..helpers import CiTestCase, FilesystemMockingTestCase
110
111 import base64
112 import gzip
113@@ -98,6 +98,33 @@ class TestWriteFiles(FilesystemMockingTestCase):
114 self.assertEqual(len(expected), flen_expected)
115
116
117+class TestDecodePerms(CiTestCase):
118+
119+ with_logs = True
120+
121+ def test_none_returns_default(self):
122+ """If None is passed as perms, then default should be returned."""
123+ default = object()
124+ found = decode_perms(None, default, self.logger)
125+ self.assertEqual(default, found)
126+
127+ def test_integer(self):
128+ """A valid integer should return itself."""
129+ found = decode_perms(0o755, None, self.logger)
130+ self.assertEqual(0o755, found)
131+
132+ def test_valid_octal_string(self):
133+ """A string should be read as octal."""
134+ found = decode_perms("644", None, self.logger)
135+ self.assertEqual(0o644, found)
136+
137+ def test_invalid_octal_string_returns_default_and_warns(self):
138+ """A string with invalid octal should warn and return default."""
139+ found = decode_perms("999", None, self.logger)
140+ self.assertIsNone(found)
141+ self.assertIn("WARNING: Undecodable", self.logs.getvalue())
142+
143+
144 def _gzip_bytes(data):
145 buf = six.BytesIO()
146 fp = None

Subscribers

People subscribed via source and target branches