launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #11507
[Merge] lp:~julian-edwards/maas/script_atomic_write into lp:maas
Julian Edwards has proposed merging lp:~julian-edwards/maas/script_atomic_write into lp:maas.
Requested reviews:
MAAS Maintainers (maas-maintainers)
For more details, see:
https://code.launchpad.net/~julian-edwards/maas/script_atomic_write/+merge/122230
This adds a script wrapper around atomic_write so that we can call it using sudo. Part one of the fix for writing the dhcpd config.
--
https://code.launchpad.net/~julian-edwards/maas/script_atomic_write/+merge/122230
Your team MAAS Maintainers is requested to review the proposed merge of lp:~julian-edwards/maas/script_atomic_write into lp:maas.
=== modified file 'src/provisioningserver/__main__.py'
--- src/provisioningserver/__main__.py 2012-08-19 23:57:42 +0000
+++ src/provisioningserver/__main__.py 2012-08-31 10:25:27 +0000
@@ -15,7 +15,7 @@
import provisioningserver.dhcp.writer
import provisioningserver.pxe.install_bootloader
import provisioningserver.pxe.install_image
-from provisioningserver.utils import MainScript
+from provisioningserver.utils import AtomicWriteScript, MainScript
main = MainScript(__doc__)
@@ -28,4 +28,7 @@
main.register(
"generate-dhcp-config",
provisioningserver.dhcp.writer)
+main.register(
+ "atomic_write",
+ AtomicWriteScript)
main()
=== modified file 'src/provisioningserver/tests/test_utils.py'
--- src/provisioningserver/tests/test_utils.py 2012-08-31 07:23:19 +0000
+++ src/provisioningserver/tests/test_utils.py 2012-08-31 10:25:27 +0000
@@ -20,7 +20,11 @@
from random import randint
import stat
import StringIO
-from subprocess import CalledProcessError
+from subprocess import (
+ CalledProcessError,
+ PIPE,
+ Popen,
+ )
import sys
import time
import types
@@ -28,9 +32,15 @@
from maastesting.factory import factory
from maastesting.fakemethod import FakeMethod
from maastesting.testcase import TestCase
-from mock import Mock
+<<<<<<< TREE
+from mock import Mock
+=======
+from mock import Mock
+import provisioningserver
+>>>>>>> MERGE-SOURCE
from provisioningserver.utils import (
ActionScript,
+ AtomicWriteScript,
atomic_write,
get_mtime,
incremental_write,
@@ -40,8 +50,15 @@
Safe,
ShellTemplate,
)
+<<<<<<< TREE
from testtools.matchers import FileContains
from testtools.testcase import ExpectedException
+=======
+from testtools.matchers import (
+ FileContains,
+ MatchesStructure,
+ )
+>>>>>>> MERGE-SOURCE
class TestSafe(TestCase):
@@ -390,3 +407,66 @@
self.assertEqual(
{"config_file": dummy_config_file, "handler": handler},
vars(namespace))
+
+
+class TestAtomicWriteScript(TestCase):
+
+ def setUp(self):
+ super(TestAtomicWriteScript, self).setUp()
+ # Silence ArgumentParser.
+ self.patch(sys, "stdout", StringIO.StringIO())
+ self.patch(sys, "stderr", StringIO.StringIO())
+
+ def get_parser(self):
+ parser = ArgumentParser()
+ AtomicWriteScript.add_arguments(parser)
+ return parser
+
+ def test_arg_setup(self):
+ parser = self.get_parser()
+ filename = factory.getRandomString()
+ args = parser.parse_args((
+ '--no-overwrite',
+ '--filename', filename))
+ self.assertThat(
+ args, MatchesStructure.byEquality(
+ no_overwrite=True,
+ filename=filename))
+
+ def test_filename_arg_required(self):
+ parser = self.get_parser()
+ self.assertRaises(SystemExit, parser.parse_args, ('--no-overwrite',))
+
+ def test_no_overwrite_defaults_to_false(self):
+ parser = self.get_parser()
+ filename = factory.getRandomString()
+ args = parser.parse_args(('--filename', filename))
+ self.assertFalse(args.no_overwrite)
+
+ def test_script_executable(self):
+ dev_root = os.path.join(
+ os.path.dirname(provisioningserver.__file__),
+ os.pardir, os.pardir)
+ content = factory.getRandomString()
+ test_data_file = self.make_file(contents=content)
+ script = ["%s/bin/maas-provision" % dev_root, 'atomic_write']
+ target_file = self.make_file()
+ script.extend(('--filename', target_file))
+ with open(test_data_file, "rb") as stdin:
+ cmd = Popen(
+ script, stdin=stdin, stdout=PIPE,
+ env=dict(PYTHONPATH=":".join(sys.path)))
+ cmd.communicate()
+ self.assertThat(target_file, FileContains(content))
+
+ def test_passes_overwrite_flag(self):
+ content = factory.getRandomString()
+ self.patch(sys, "stdin", StringIO.StringIO(content))
+ parser = self.get_parser()
+ filename = factory.getRandomString()
+ args = parser.parse_args(('--filename', filename, '--no-overwrite'))
+ mock = Mock()
+ self.patch(provisioningserver.utils, 'atomic_write', mock)
+ AtomicWriteScript.run(args)
+
+ mock.assert_called_once_with(content, filename, overwrite=False)
=== modified file 'src/provisioningserver/utils.py'
--- src/provisioningserver/utils.py 2012-08-31 06:59:14 +0000
+++ src/provisioningserver/utils.py 2012-08-31 10:25:27 +0000
@@ -302,3 +302,33 @@
"-c", "--config-file", metavar="FILENAME",
help="Configuration file to load [%(default)s].",
default=Config.DEFAULT_FILENAME)
+
+
+class AtomicWriteScript:
+ """Wrap the atomic_write function turning it into an ActionScript.
+
+ To use:
+ >>> main = MainScript(atomic_write.__doc__)
+ >>> main.register("myscriptname", AtomicWriteScript)
+ >>> main()
+ """
+
+ @staticmethod
+ def add_arguments(parser):
+ """Initialise options for writing files atomically.
+
+ :param parser: An instance of :class:`ArgumentParser`.
+ """
+ parser.add_argument(
+ "--no-overwrite", action="store_true", required=False,
+ default=False, help="Don't overwrite file if it exists")
+ parser.add_argument(
+ "--filename", action="store", required=True, help=(
+ "The name of the file in which to store contents of stdin"))
+
+ @staticmethod
+ def run(args):
+ """Take content from stdin and write it atomically to a file."""
+ content = sys.stdin.read()
+ atomic_write(
+ content, args.filename, overwrite=not args.no_overwrite)
Follow ups