Merge ~larsks/cloud-init:lp/1644064 into cloud-init:master

Proposed by Lars Kellogg-Stedman
Status: Merged
Merged at revision: 721348a622a660b65acfdf7fdf53203b47f80748
Proposed branch: ~larsks/cloud-init:lp/1644064
Merge into: cloud-init:master
Diff against target: 122 lines (+53/-5)
4 files modified
cloudinit/atomic_helper.py (+11/-1)
cloudinit/config/cc_set_passwords.py (+2/-1)
cloudinit/util.py (+9/-1)
tests/unittests/test_util.py (+31/-2)
Reviewer Review Type Date Requested Status
Server Team CI bot continuous-integration Approve
Scott Moser Needs Fixing
Review via email: mp+322309@code.launchpad.net

Description of the change

util: teach write_file to default to existing file mode

On centos/fedora/rhel/derivatives, /etc/ssh/sshd_config has mode 0600,
but cloud-init unilaterally sets file modes to 0644 when no explicit
mode is passed to util.write_file. With this patch, write_file will
by default check if the target file already exists, and if so will use
the mode of that file by default unless an explicit mode is passed by
the caller.

To post a comment you must log in.
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 :

This looks good, but there is one issue, in that you "fixed" /etc/ssh/sshd_config's permissions
by changing the behavior of write_file(filename, "foo").

That means all callers that do not pass 'mode' will now have (possibly) changed behavior.

The other issue here is that there is no way for the caller to say "use existing, but if new, use 0o755".

we could change the signature to include a 'reuse_existing_mode' (with open bikeshedding for a better name) or some other way indicate that the perms on the existing file should be used if it exists.

Lastly... atomic_helper.write_file currently has the same signature as util.write_file and i'd like to keep that, do lets plan on updating it as well.

review: Needs Fixing
Revision history for this message
Lars Kellogg-Stedman (larsks) wrote :

I've submitted an update to this patch that:

- introduces a new "copy_mode" argument to write_file (which defaults to False)
- updates the test to reflect the new parameter
- updates config/cc_set_passwords.py to pass copy_mode=True to write_file when writing sshd_config
- updates write_file in atomic_helper to behave the same way

> The other issue here is that there is no way for the caller to say "use existing, but if new, use 0o755".

This is now possible.

Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Needs Fixing (continuous-integration)
Revision history for this message
Lars Kellogg-Stedman (larsks) wrote :

Aaaaand fixed the pep8 error.

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 :

The commit message now needs update to match the code changes.

The code changes and tests look good though. thank you.

review: Needs Fixing
Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Lars Kellogg-Stedman (larsks) wrote :

I've updated the commit message.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/cloudinit/atomic_helper.py b/cloudinit/atomic_helper.py
2index fb2df8d..587b994 100644
3--- a/cloudinit/atomic_helper.py
4+++ b/cloudinit/atomic_helper.py
5@@ -2,13 +2,23 @@
6
7 import json
8 import os
9+import stat
10 import tempfile
11
12 _DEF_PERMS = 0o644
13
14
15-def write_file(filename, content, mode=_DEF_PERMS, omode="wb"):
16+def write_file(filename, content, mode=_DEF_PERMS,
17+ omode="wb", copy_mode=False):
18 # open filename in mode 'omode', write content, set permissions to 'mode'
19+
20+ if copy_mode:
21+ try:
22+ file_stat = os.stat(filename)
23+ mode = stat.S_IMODE(file_stat.st_mode)
24+ except OSError:
25+ pass
26+
27 tf = None
28 try:
29 tf = tempfile.NamedTemporaryFile(dir=os.path.dirname(filename),
30diff --git a/cloudinit/config/cc_set_passwords.py b/cloudinit/config/cc_set_passwords.py
31index eb0bdab..bb24d57 100755
32--- a/cloudinit/config/cc_set_passwords.py
33+++ b/cloudinit/config/cc_set_passwords.py
34@@ -215,7 +215,8 @@ def handle(_name, cfg, cloud, log, args):
35 pw_auth))
36
37 lines = [str(l) for l in new_lines]
38- util.write_file(ssh_util.DEF_SSHD_CFG, "\n".join(lines))
39+ util.write_file(ssh_util.DEF_SSHD_CFG, "\n".join(lines),
40+ copy_mode=True)
41
42 try:
43 cmd = cloud.distro.init_cmd # Default service
44diff --git a/cloudinit/util.py b/cloudinit/util.py
45index 17abdf8..6940850 100644
46--- a/cloudinit/util.py
47+++ b/cloudinit/util.py
48@@ -1688,7 +1688,7 @@ def chmod(path, mode):
49 os.chmod(path, real_mode)
50
51
52-def write_file(filename, content, mode=0o644, omode="wb"):
53+def write_file(filename, content, mode=0o644, omode="wb", copy_mode=False):
54 """
55 Writes a file with the given content and sets the file mode as specified.
56 Resotres the SELinux context if possible.
57@@ -1698,6 +1698,14 @@ def write_file(filename, content, mode=0o644, omode="wb"):
58 @param mode: The filesystem mode to set on the file.
59 @param omode: The open mode used when opening the file (w, wb, a, etc.)
60 """
61+
62+ if copy_mode:
63+ try:
64+ file_stat = os.stat(filename)
65+ mode = stat.S_IMODE(file_stat.st_mode)
66+ except OSError:
67+ pass
68+
69 ensure_dir(os.path.dirname(filename))
70 if 'b' in omode.lower():
71 content = encode_text(content)
72diff --git a/tests/unittests/test_util.py b/tests/unittests/test_util.py
73index ab74311..5d21b4b 100644
74--- a/tests/unittests/test_util.py
75+++ b/tests/unittests/test_util.py
76@@ -103,8 +103,8 @@ class TestWriteFile(helpers.TestCase):
77 self.assertTrue(os.path.isdir(dirname))
78 self.assertTrue(os.path.isfile(path))
79
80- def test_custom_mode(self):
81- """Verify custom mode works properly."""
82+ def test_explicit_mode(self):
83+ """Verify explicit file mode works properly."""
84 path = os.path.join(self.tmp, "NewFile.txt")
85 contents = "Hey there"
86
87@@ -115,6 +115,35 @@ class TestWriteFile(helpers.TestCase):
88 file_stat = os.stat(path)
89 self.assertEqual(0o666, stat.S_IMODE(file_stat.st_mode))
90
91+ def test_copy_mode_no_existing(self):
92+ """Verify that file is created with mode 0o644 if copy_mode
93+ is true and there is no prior existing file."""
94+ path = os.path.join(self.tmp, "NewFile.txt")
95+ contents = "Hey there"
96+
97+ util.write_file(path, contents, copy_mode=True)
98+
99+ self.assertTrue(os.path.exists(path))
100+ self.assertTrue(os.path.isfile(path))
101+ file_stat = os.stat(path)
102+ self.assertEqual(0o644, stat.S_IMODE(file_stat.st_mode))
103+
104+ def test_copy_mode_with_existing(self):
105+ """Verify that file is created using mode of existing file
106+ if copy_mode is true."""
107+ path = os.path.join(self.tmp, "NewFile.txt")
108+ contents = "Hey there"
109+
110+ open(path, 'w').close()
111+ os.chmod(path, 0o666)
112+
113+ util.write_file(path, contents, copy_mode=True)
114+
115+ self.assertTrue(os.path.exists(path))
116+ self.assertTrue(os.path.isfile(path))
117+ file_stat = os.stat(path)
118+ self.assertEqual(0o666, stat.S_IMODE(file_stat.st_mode))
119+
120 def test_custom_omode(self):
121 """Verify custom omode works properly."""
122 path = os.path.join(self.tmp, "NewFile.txt")

Subscribers

People subscribed via source and target branches