Merge ~smoser/cloud-init:bug/1707222-use-run-for-tmpdir into cloud-init:master

Proposed by Scott Moser on 2017-08-29
Status: Merged
Approved by: Scott Moser on 2017-09-07
Approved revision: 41ef7485d1589a7c4a379763d4e4fdcc6d88d12b
Merged at revision: 409918f9ba83e45e9bc5cc0b6c589e2fc8ae9b60
Proposed branch: ~smoser/cloud-init:bug/1707222-use-run-for-tmpdir
Merge into: cloud-init:master
Diff against target: 355 lines (+112/-46)
10 files modified
cloudinit/config/cc_bootcmd.py (+2/-1)
cloudinit/config/cc_chef.py (+2/-1)
cloudinit/config/cc_snappy.py (+2/-2)
cloudinit/net/dhcp.py (+2/-1)
cloudinit/sources/helpers/azure.py (+2/-2)
cloudinit/temp_utils.py (+93/-0)
cloudinit/util.py (+2/-34)
packages/bddeb (+3/-2)
tests/unittests/test_datasource/test_azure_helper.py (+2/-2)
tests/unittests/test_net.py (+2/-1)
Reviewer Review Type Date Requested Status
Ryan Harper 2017-08-29 Approve on 2017-09-07
Chad Smith Approve on 2017-09-07
Server Team CI bot continuous-integration Approve on 2017-08-31
Review via email: mp+329811@code.launchpad.net

Commit Message

Use /run/cloud-init for tempfile operations.

During boot, the usage of /tmp is not safe. In systemd systems,
systemd-tmpfiles-clean may run at any point and clear out a temp file
while cloud-init is using it. The solution was to use
/run/cloud-init/tmp.

LP: #1707222

To post a comment you must log in.
Scott Moser (smoser) wrote :

Issues with this currently are
a.) no unit tests of the _TEMPDIR path using /run/cloud-init.
b.) ExtendedTemporaryFile was using util.del_file, but I did not want a circular import to cloudinit.util
c.) could clean up /run/cloud-init via atexit
d.) tempdir() was using del_dir, but circular import.

FAILED: Continuous integration, rev:4b4e53977755b00b6105dafaeb117c260b0bf7d5
https://jenkins.ubuntu.com/server/job/cloud-init-ci/220/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    FAILED: Ubuntu LTS: Build

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/220/rebuild

review: Needs Fixing (continuous-integration)

FAILED: Continuous integration, rev:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/222/
Executed test runs:
    SUCCESS: Checkout
    FAILED: Unit & Style Tests

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/222/rebuild

review: Needs Fixing (continuous-integration)

PASSED: Continuous integration, rev:b6e31f58f80f85b9b5c14bc6f5b0f47d7b71ad5e
https://jenkins.ubuntu.com/server/job/cloud-init-ci/221/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    SUCCESS: MAAS Compatability Testing
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/221/rebuild

review: Approve (continuous-integration)

PASSED: Continuous integration, rev:6150f71ba59f92b6d5bb179f1b78c6235d37ba93
https://jenkins.ubuntu.com/server/job/cloud-init-ci/223/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    SUCCESS: MAAS Compatability Testing
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/223/rebuild

review: Approve (continuous-integration)
Ryan Harper (raharper) :
review: Needs Fixing
Chad Smith (chad.smith) :
Chad Smith (chad.smith) :
189c266... by Scott Moser on 2017-08-30

add 'param' doc for odir

3fcdeca... by Scott Moser on 2017-08-30

address dedent feedback, and actually assign to _TMPDIR

90724e4... by Scott Moser on 2017-08-30

try/except to avoid race of check then remove.

Scott Moser (smoser) wrote :

i think i've addressed all comments.

5ac4d45... by Scott Moser on 2017-08-30

correct errno usage

PASSED: Continuous integration, rev:1020f5eff24fc2be551e027fee982c4895eb20a7
https://jenkins.ubuntu.com/server/job/cloud-init-ci/231/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    SUCCESS: MAAS Compatability Testing
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/231/rebuild

review: Approve (continuous-integration)

PASSED: Continuous integration, rev:b2c155ef261d584647e7b2d1546507bb6de5de5d
https://jenkins.ubuntu.com/server/job/cloud-init-ci/233/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    SUCCESS: MAAS Compatability Testing
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/233/rebuild

review: Approve (continuous-integration)
Ryan Harper (raharper) wrote :

Looks good, minor item and one question inline below.

Chad Smith (chad.smith) :
Chad Smith (chad.smith) :
a9ff924... by Scott Moser on 2017-08-31

file header and vi footer on temp_utils.py

7eacf50... by Scott Moser on 2017-08-31

improve comment in _tmpfile_dir_arg

Scott Moser (smoser) wrote :

adjusted to feedback. i'm not sure what to do about the Paths statement that Chad raised.

FAILED: Continuous integration, rev:3e8b05f3ec26e4bb97961091c2125f401fbb64b6
https://jenkins.ubuntu.com/server/job/cloud-init-ci/237/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    FAILED: Ubuntu LTS: Build

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/237/rebuild

review: Needs Fixing (continuous-integration)

PASSED: Continuous integration, rev:41ef7485d1589a7c4a379763d4e4fdcc6d88d12b
https://jenkins.ubuntu.com/server/job/cloud-init-ci/239/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    SUCCESS: MAAS Compatability Testing
    IN_PROGRESS: Declarative: Post Actions

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/239/rebuild

review: Approve (continuous-integration)
Chad Smith (chad.smith) wrote :

> adjusted to feedback. i'm not sure what to do about the Paths statement that
> Chad raised.in

Scott, let's not block on my paths comment for now. We can sort it in a followup branch if the need becomes greater. It might involve us reworking how Paths object is initialized which would allow both paths and this to source the same static config variables/globals. It

Chad Smith (chad.smith) wrote :

It might be nice to cover some of this in unittesting, but I can take a stab at it on a cc_bootcmd schema branch I'm working.

Scott Moser (smoser) wrote :

marked approved per Chad's last comment there.

Chad Smith (chad.smith) :
review: Approve
c778531... by Scott Moser on 2017-09-07

docstring fix.

Ryan Harper (raharper) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/cloudinit/config/cc_bootcmd.py b/cloudinit/config/cc_bootcmd.py
2index 604f93b..9c0476a 100644
3--- a/cloudinit/config/cc_bootcmd.py
4+++ b/cloudinit/config/cc_bootcmd.py
5@@ -37,6 +37,7 @@ specified either as lists or strings. For invocation details, see ``runcmd``.
6 import os
7
8 from cloudinit.settings import PER_ALWAYS
9+from cloudinit import temp_utils
10 from cloudinit import util
11
12 frequency = PER_ALWAYS
13@@ -49,7 +50,7 @@ def handle(name, cfg, cloud, log, _args):
14 " no 'bootcmd' key in configuration"), name)
15 return
16
17- with util.ExtendedTemporaryFile(suffix=".sh") as tmpf:
18+ with temp_utils.ExtendedTemporaryFile(suffix=".sh") as tmpf:
19 try:
20 content = util.shellify(cfg["bootcmd"])
21 tmpf.write(util.encode_text(content))
22diff --git a/cloudinit/config/cc_chef.py b/cloudinit/config/cc_chef.py
23index 02c70b1..c192dd3 100644
24--- a/cloudinit/config/cc_chef.py
25+++ b/cloudinit/config/cc_chef.py
26@@ -71,6 +71,7 @@ import itertools
27 import json
28 import os
29
30+from cloudinit import temp_utils
31 from cloudinit import templater
32 from cloudinit import url_helper
33 from cloudinit import util
34@@ -303,7 +304,7 @@ def install_chef(cloud, chef_cfg, log):
35 "omnibus_url_retries",
36 default=OMNIBUS_URL_RETRIES))
37 content = url_helper.readurl(url=url, retries=retries).contents
38- with util.tempdir() as tmpd:
39+ with temp_utils.tempdir() as tmpd:
40 # Use tmpdir over tmpfile to avoid 'text file busy' on execute
41 tmpf = "%s/chef-omnibus-install" % tmpd
42 util.write_file(tmpf, content, mode=0o700)
43diff --git a/cloudinit/config/cc_snappy.py b/cloudinit/config/cc_snappy.py
44index a9682f1..eecb817 100644
45--- a/cloudinit/config/cc_snappy.py
46+++ b/cloudinit/config/cc_snappy.py
47@@ -63,11 +63,11 @@ is ``auto``. Options are:
48
49 from cloudinit import log as logging
50 from cloudinit.settings import PER_INSTANCE
51+from cloudinit import temp_utils
52 from cloudinit import util
53
54 import glob
55 import os
56-import tempfile
57
58 LOG = logging.getLogger(__name__)
59
60@@ -183,7 +183,7 @@ def render_snap_op(op, name, path=None, cfgfile=None, config=None):
61 # config
62 # Note, however, we do not touch config files on disk.
63 nested_cfg = {'config': {shortname: config}}
64- (fd, cfg_tmpf) = tempfile.mkstemp()
65+ (fd, cfg_tmpf) = temp_utils.mkstemp()
66 os.write(fd, util.yaml_dumps(nested_cfg).encode())
67 os.close(fd)
68 cfgfile = cfg_tmpf
69diff --git a/cloudinit/net/dhcp.py b/cloudinit/net/dhcp.py
70index c7febc5..c842c83 100644
71--- a/cloudinit/net/dhcp.py
72+++ b/cloudinit/net/dhcp.py
73@@ -9,6 +9,7 @@ import os
74 import re
75
76 from cloudinit.net import find_fallback_nic, get_devicelist
77+from cloudinit import temp_utils
78 from cloudinit import util
79
80 LOG = logging.getLogger(__name__)
81@@ -47,7 +48,7 @@ def maybe_perform_dhcp_discovery(nic=None):
82 if not dhclient_path:
83 LOG.debug('Skip dhclient configuration: No dhclient command found.')
84 return {}
85- with util.tempdir(prefix='cloud-init-dhcp-') as tmpdir:
86+ with temp_utils.tempdir(prefix='cloud-init-dhcp-') as tmpdir:
87 return dhcp_discovery(dhclient_path, nic, tmpdir)
88
89
90diff --git a/cloudinit/sources/helpers/azure.py b/cloudinit/sources/helpers/azure.py
91index e22409d..28ed0ae 100644
92--- a/cloudinit/sources/helpers/azure.py
93+++ b/cloudinit/sources/helpers/azure.py
94@@ -6,10 +6,10 @@ import os
95 import re
96 import socket
97 import struct
98-import tempfile
99 import time
100
101 from cloudinit import stages
102+from cloudinit import temp_utils
103 from contextlib import contextmanager
104 from xml.etree import ElementTree
105
106@@ -111,7 +111,7 @@ class OpenSSLManager(object):
107 }
108
109 def __init__(self):
110- self.tmpdir = tempfile.mkdtemp()
111+ self.tmpdir = temp_utils.mkdtemp()
112 self.certificate = None
113 self.generate_certificate()
114
115diff --git a/cloudinit/temp_utils.py b/cloudinit/temp_utils.py
116new file mode 100644
117index 0000000..0355f19
118--- /dev/null
119+++ b/cloudinit/temp_utils.py
120@@ -0,0 +1,93 @@
121+# This file is part of cloud-init. See LICENSE file for license information.
122+
123+import contextlib
124+import errno
125+import os
126+import shutil
127+import tempfile
128+
129+_TMPDIR = None
130+_ROOT_TMPDIR = "/run/cloud-init/tmp"
131+
132+
133+def _tempfile_dir_arg(odir=None):
134+ """Return the proper 'dir' argument for tempfile functions.
135+
136+ When root, cloud-init will use /run/cloud-init/tmp to avoid
137+ any cleaning that a distro boot might do on /tmp (such as
138+ systemd-tmpfiles-clean).
139+
140+ If the caller of this function (mkdtemp or mkstemp) was provided
141+ with a 'dir' argument, then that is respected.
142+
143+ @param odir: original 'dir' arg to 'mkdtemp' or other."""
144+
145+ if odir is not None:
146+ return odir
147+
148+ global _TMPDIR
149+ if _TMPDIR:
150+ return _TMPDIR
151+
152+ if os.getuid() == 0:
153+ tdir = _ROOT_TMPDIR
154+ else:
155+ tdir = os.environ.get('TMPDIR', '/tmp')
156+ if not os.path.isdir(tdir):
157+ os.makedirs(tdir)
158+ os.chmod(tdir, 0o1777)
159+
160+ _TMPDIR = tdir
161+ return tdir
162+
163+
164+def ExtendedTemporaryFile(**kwargs):
165+ kwargs['dir'] = _tempfile_dir_arg(kwargs.pop('dir', None))
166+ fh = tempfile.NamedTemporaryFile(**kwargs)
167+ # Replace its unlink with a quiet version
168+ # that does not raise errors when the
169+ # file to unlink has been unlinked elsewhere..
170+
171+ def _unlink_if_exists(path):
172+ try:
173+ os.unlink(path)
174+ except OSError as e:
175+ if e.errno != errno.ENOENT:
176+ raise e
177+
178+ fh.unlink = _unlink_if_exists
179+
180+ # Add a new method that will unlink
181+ # right 'now' but still lets the exit
182+ # method attempt to remove it (which will
183+ # not throw due to our del file being quiet
184+ # about files that are not there)
185+ def unlink_now():
186+ fh.unlink(fh.name)
187+
188+ setattr(fh, 'unlink_now', unlink_now)
189+ return fh
190+
191+
192+@contextlib.contextmanager
193+def tempdir(**kwargs):
194+ # This seems like it was only added in python 3.2
195+ # Make it since its useful...
196+ # See: http://bugs.python.org/file12970/tempdir.patch
197+ tdir = mkdtemp(**kwargs)
198+ try:
199+ yield tdir
200+ finally:
201+ shutil.rmtree(tdir)
202+
203+
204+def mkdtemp(**kwargs):
205+ kwargs['dir'] = _tempfile_dir_arg(kwargs.pop('dir', None))
206+ return tempfile.mkdtemp(**kwargs)
207+
208+
209+def mkstemp(**kwargs):
210+ kwargs['dir'] = _tempfile_dir_arg(kwargs.pop('dir', None))
211+ return tempfile.mkstemp(**kwargs)
212+
213+# vi: ts=4 expandtab
214diff --git a/cloudinit/util.py b/cloudinit/util.py
215index 609e94c..ae5cda8 100644
216--- a/cloudinit/util.py
217+++ b/cloudinit/util.py
218@@ -30,7 +30,6 @@ import stat
219 import string
220 import subprocess
221 import sys
222-import tempfile
223 import time
224
225 from errno import ENOENT, ENOEXEC
226@@ -45,6 +44,7 @@ from cloudinit import importer
227 from cloudinit import log as logging
228 from cloudinit import mergers
229 from cloudinit import safeyaml
230+from cloudinit import temp_utils
231 from cloudinit import type_utils
232 from cloudinit import url_helper
233 from cloudinit import version
234@@ -349,26 +349,6 @@ class DecompressionError(Exception):
235 pass
236
237
238-def ExtendedTemporaryFile(**kwargs):
239- fh = tempfile.NamedTemporaryFile(**kwargs)
240- # Replace its unlink with a quiet version
241- # that does not raise errors when the
242- # file to unlink has been unlinked elsewhere..
243- LOG.debug("Created temporary file %s", fh.name)
244- fh.unlink = del_file
245-
246- # Add a new method that will unlink
247- # right 'now' but still lets the exit
248- # method attempt to remove it (which will
249- # not throw due to our del file being quiet
250- # about files that are not there)
251- def unlink_now():
252- fh.unlink(fh.name)
253-
254- setattr(fh, 'unlink_now', unlink_now)
255- return fh
256-
257-
258 def fork_cb(child_cb, *args, **kwargs):
259 fid = os.fork()
260 if fid == 0:
261@@ -790,18 +770,6 @@ def umask(n_msk):
262 os.umask(old)
263
264
265-@contextlib.contextmanager
266-def tempdir(**kwargs):
267- # This seems like it was only added in python 3.2
268- # Make it since its useful...
269- # See: http://bugs.python.org/file12970/tempdir.patch
270- tdir = tempfile.mkdtemp(**kwargs)
271- try:
272- yield tdir
273- finally:
274- del_dir(tdir)
275-
276-
277 def center(text, fill, max_len):
278 return '{0:{fill}{align}{size}}'.format(text, fill=fill,
279 align="^", size=max_len)
280@@ -1587,7 +1555,7 @@ def mount_cb(device, callback, data=None, rw=False, mtype=None, sync=True):
281 mtypes = ['']
282
283 mounted = mounts()
284- with tempdir() as tmpd:
285+ with temp_utils.tempdir() as tmpd:
286 umount = False
287 if os.path.realpath(device) in mounted:
288 mountpoint = mounted[os.path.realpath(device)]['mountpoint']
289diff --git a/packages/bddeb b/packages/bddeb
290index 7c12354..4f2e2dd 100755
291--- a/packages/bddeb
292+++ b/packages/bddeb
293@@ -21,8 +21,9 @@ def find_root():
294 if "avoid-pep8-E402-import-not-top-of-file":
295 # Use the util functions from cloudinit
296 sys.path.insert(0, find_root())
297- from cloudinit import templater
298 from cloudinit import util
299+ from cloudinit import temp_utils
300+ from cloudinit import templater
301
302 DEBUILD_ARGS = ["-S", "-d"]
303
304@@ -148,7 +149,7 @@ def main():
305 capture = False
306
307 templ_data = {'debian_release': args.release}
308- with util.tempdir() as tdir:
309+ with temp_utils.tempdir() as tdir:
310
311 # output like 0.7.6-1022-g36e92d3
312 ver_data = read_version()
313diff --git a/tests/unittests/test_datasource/test_azure_helper.py b/tests/unittests/test_datasource/test_azure_helper.py
314index 80ce003..44b99ec 100644
315--- a/tests/unittests/test_datasource/test_azure_helper.py
316+++ b/tests/unittests/test_datasource/test_azure_helper.py
317@@ -275,7 +275,7 @@ class TestOpenSSLManager(TestCase):
318 mock.patch('builtins.open'))
319
320 @mock.patch.object(azure_helper, 'cd', mock.MagicMock())
321- @mock.patch.object(azure_helper.tempfile, 'mkdtemp')
322+ @mock.patch.object(azure_helper.temp_utils, 'mkdtemp')
323 def test_openssl_manager_creates_a_tmpdir(self, mkdtemp):
324 manager = azure_helper.OpenSSLManager()
325 self.assertEqual(mkdtemp.return_value, manager.tmpdir)
326@@ -292,7 +292,7 @@ class TestOpenSSLManager(TestCase):
327 manager.clean_up()
328
329 @mock.patch.object(azure_helper, 'cd', mock.MagicMock())
330- @mock.patch.object(azure_helper.tempfile, 'mkdtemp', mock.MagicMock())
331+ @mock.patch.object(azure_helper.temp_utils, 'mkdtemp', mock.MagicMock())
332 @mock.patch.object(azure_helper.util, 'del_dir')
333 def test_clean_up(self, del_dir):
334 manager = azure_helper.OpenSSLManager()
335diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py
336index c10ef90..f249615 100644
337--- a/tests/unittests/test_net.py
338+++ b/tests/unittests/test_net.py
339@@ -9,6 +9,7 @@ from cloudinit.net import network_state
340 from cloudinit.net import renderers
341 from cloudinit.net import sysconfig
342 from cloudinit.sources.helpers import openstack
343+from cloudinit import temp_utils
344 from cloudinit import util
345
346 from cloudinit.tests.helpers import CiTestCase
347@@ -2150,7 +2151,7 @@ class TestCmdlineConfigParsing(CiTestCase):
348 static['mac_address'] = macs['eth1']
349
350 expected = {'version': 1, 'config': [dhcp, static]}
351- with util.tempdir() as tmpd:
352+ with temp_utils.tempdir() as tmpd:
353 for fname, content in pairs:
354 fp = os.path.join(tmpd, fname)
355 files.append(fp)

Subscribers

People subscribed via source and target branches

to all changes: