← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~jtv/maas/customize-config into lp:maas

 

Jeroen T. Vermeulen has proposed merging lp:~jtv/maas/customize-config into lp:maas.

Requested reviews:
  MAAS Maintainers (maas-maintainers)

For more details, see:
https://code.launchpad.net/~jtv/maas/customize-config/+merge/123477

Based on discussion with Scott, and reviews from predecessors to this branch.


Jeroen
-- 
https://code.launchpad.net/~jtv/maas/customize-config/+merge/123477
Your team MAAS Maintainers is requested to review the proposed merge of lp:~jtv/maas/customize-config into lp:maas.
=== modified file 'src/provisioningserver/__main__.py'
--- src/provisioningserver/__main__.py	2012-09-03 05:20:39 +0000
+++ src/provisioningserver/__main__.py	2012-09-10 04:49:20 +0000
@@ -12,6 +12,7 @@
 
 __metaclass__ = type
 
+import provisioningserver.customize_config
 import provisioningserver.dhcp.writer
 import provisioningserver.pxe.install_bootloader
 import provisioningserver.pxe.install_image
@@ -21,17 +22,16 @@
     )
 
 
+script_commands = {
+    'atomic-write': AtomicWriteScript,
+    'customize-config': provisioningserver.customize_config,
+    'generate-dhcp-config': provisioningserver.dhcp.writer,
+    'install-pxe-bootloader': provisioningserver.pxe.install_bootloader,
+    'install-pxe-image': provisioningserver.pxe.install_image,
+}
+
+
 main = MainScript(__doc__)
-main.register(
-    "install-pxe-bootloader",
-    provisioningserver.pxe.install_bootloader)
-main.register(
-    "install-pxe-image",
-    provisioningserver.pxe.install_image)
-main.register(
-    "generate-dhcp-config",
-    provisioningserver.dhcp.writer)
-main.register(
-    "atomic-write",
-    AtomicWriteScript)
+for name, command in sorted(script_commands.items()):
+    main.register(name, command)
 main()

=== added file 'src/provisioningserver/customize_config.py'
--- src/provisioningserver/customize_config.py	1970-01-01 00:00:00 +0000
+++ src/provisioningserver/customize_config.py	2012-09-10 04:49:20 +0000
@@ -0,0 +1,49 @@
+# Copyright 2012 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Management command: customize a config file.
+
+Use this when there's absolutely no way around adding a custom MAAS section
+to an existing config file.  It appends the custom section on first run, but
+on subsequent runs, replaces the existing custom section in-place.
+"""
+
+from __future__ import (
+    absolute_import,
+    print_function,
+    unicode_literals,
+    )
+
+__metaclass__ = type
+__all__ = [
+    'add_arguments',
+    'run',
+    ]
+
+import sys
+
+from provisioningserver.utils import write_custom_config_section
+
+
+def add_arguments(parser):
+    parser.add_argument(
+        'file', metavar='FILE',
+        help="Configuration file that you want to customize.")
+    parser.add_argument(
+        '--encoding', dest='encoding', default='utf-8',
+        help="Encoding to use when reading and writing config.")
+
+
+def run(args):
+    """Customize a config file.
+
+    Reads a custom configuration section from standard input, and the given
+    configuration file.  Prints to standard output a copy of the file with
+    the custom section appended, or substituted for an existing custom
+    section if there already was one.
+    """
+    with open(args.file, 'rb') as original_file:
+        original_text = original_file.read().decode(args.encoding)
+    custom_section = sys.stdin.read().decode(args.encoding)
+    new_text = write_custom_config_section(original_text, custom_section)
+    sys.stdout.write(new_text.encode(args.encoding))

=== added file 'src/provisioningserver/tests/test_customize_config.py'
--- src/provisioningserver/tests/test_customize_config.py	1970-01-01 00:00:00 +0000
+++ src/provisioningserver/tests/test_customize_config.py	2012-09-10 04:49:20 +0000
@@ -0,0 +1,85 @@
+# Copyright 2012 Canonical Ltd.  This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Tests for customize_config."""
+
+from __future__ import (
+    absolute_import,
+    print_function,
+    unicode_literals,
+    )
+
+__metaclass__ = type
+__all__ = []
+
+from argparse import ArgumentParser
+from io import BytesIO
+import os.path
+from subprocess import (
+    PIPE,
+    Popen,
+    )
+import sys
+from textwrap import dedent
+
+from maastesting.factory import factory
+from maastesting.testcase import TestCase
+import provisioningserver
+from provisioningserver import customize_config
+from provisioningserver.utils import maas_custom_config_markers
+
+
+def locate_dev_root():
+    """Return root of development source tree."""
+    return os.path.join(
+        os.path.dirname(provisioningserver.__file__),
+        os.pardir, os.pardir)
+
+
+class TestCustomizeConfig(TestCase):
+
+    def run_command(self, input_file, stdin):
+        self.patch(sys, 'stdin', BytesIO(stdin.encode('utf-8')))
+        self.patch(sys, 'stdout', BytesIO())
+        parser = ArgumentParser()
+        customize_config.add_arguments(parser)
+        parsed_args = parser.parse_args((input_file, ))
+        customize_config.run(parsed_args)
+
+    def test_runs_as_script(self):
+        original_text = factory.getRandomString()
+        original_file = self.make_file(original_text)
+        script = "%s/bin/maas-provision" % locate_dev_root()
+        command = Popen(
+            [script, "customize-config", original_file],
+            stdin=PIPE, stdout=PIPE,
+            env=dict(PYTHONPATH=":".join(sys.path)))
+        command.communicate(original_text)
+        self.assertEqual(0, command.returncode)
+
+    def test_produces_sensible_text(self):
+        header, footer = maas_custom_config_markers
+        original_file = self.make_file(contents="Original text here.")
+
+        self.run_command(original_file, stdin="Custom section here.")
+
+        sys.stdout.seek(0)
+        expected = dedent("""\
+            Original text here.
+            %s
+            Custom section here.
+            %s
+            """) % (header, footer)
+        output = sys.stdout.read()
+        self.assertEqual(expected, output.decode('utf-8'))
+
+    def test_does_not_modify_original(self):
+        original_text = factory.getRandomString().encode('ascii')
+        original_file = self.make_file(contents=original_text)
+
+        self.run_command(original_file, factory.getRandomString())
+
+        with open(original_file, 'rb') as reread_file:
+            contents_after = reread_file.read()
+
+        self.assertEqual(original_text, contents_after)