فهرست منبع

In generate-borgmatic-config, comment out all optional config (#57).

Dan Helfman 6 سال پیش
والد
کامیت
47efa88c9d
5فایلهای تغییر یافته به همراه141 افزوده شده و 21 حذف شده
  1. 4 0
      NEWS
  2. 1 1
      README.md
  3. 57 6
      borgmatic/config/generate.py
  4. 10 10
      borgmatic/config/schema.yaml
  5. 69 4
      borgmatic/tests/integration/config/test_generate.py

+ 4 - 0
NEWS

@@ -1,3 +1,7 @@
+1.2.5
+ * #57: When generating sample configuration with generate-borgmatic-config, comment out all
+   optional configuration so as to streamline the initial configuration process.
+
 1.2.4
 1.2.4
  * Fix for archive checking traceback due to parameter mismatch.
  * Fix for archive checking traceback due to parameter mismatch.
 
 

+ 1 - 1
README.md

@@ -108,7 +108,7 @@ not in your system `PATH`. Try looking in `/usr/local/bin/`.
 This generates a sample configuration file at /etc/borgmatic/config.yaml (by
 This generates a sample configuration file at /etc/borgmatic/config.yaml (by
 default). You should edit the file to suit your needs, as the values are just
 default). You should edit the file to suit your needs, as the values are just
 representative. All fields are optional except where indicated, so feel free
 representative. All fields are optional except where indicated, so feel free
-to remove anything you don't need.
+to ignore anything you don't need.
 
 
 You can also have a look at the [full configuration
 You can also have a look at the [full configuration
 schema](https://projects.torsion.org/witten/borgmatic/src/master/borgmatic/config/schema.yaml)
 schema](https://projects.torsion.org/witten/borgmatic/src/master/borgmatic/config/schema.yaml)

+ 57 - 6
borgmatic/config/generate.py

@@ -9,7 +9,7 @@ INDENT = 4
 
 
 def _insert_newline_before_comment(config, field_name):
 def _insert_newline_before_comment(config, field_name):
     '''
     '''
-    Using some ruamel.yaml black magic, insert a blank line in the config right befor the given
+    Using some ruamel.yaml black magic, insert a blank line in the config right before the given
     field and its comments.
     field and its comments.
     '''
     '''
     config.ca.items[field_name][1].insert(
     config.ca.items[field_name][1].insert(
@@ -40,10 +40,58 @@ def _schema_to_sample_configuration(schema, level=0):
     return config
     return config
 
 
 
 
-def write_configuration(config_filename, config, mode=0o600):
+def _comment_out_line(line):
+    # If it's already is commented out (or empty), there's nothing further to do!
+    stripped_line = line.lstrip()
+    if not stripped_line or stripped_line.startswith('#'):
+        return line
+
+    # Comment out the names of optional sections.
+    one_indent = ' ' * INDENT
+    if not line.startswith(one_indent):
+        return '#' + line
+
+    # Otherwise, comment out the line, but insert the "#" after the first indent for aesthetics.
+    return '#'.join((one_indent, line[INDENT:]))
+
+
+def _comment_out_optional_configuration(rendered_config):
     '''
     '''
-    Given a target config filename and a config data structure of nested OrderedDicts, write out the
-    config to file as YAML. Create any containing directories as needed.
+    Post-process a rendered configuration string to comment out optional key/values. The idea is
+    that this prevents the user from having to comment out a bunch of configuration they don't care
+    about to get to a minimal viable configuration file.
+
+    Ideally ruamel.yaml would support this during configuration generation, but it's not terribly
+    easy to accomplish that way.
+    '''
+    lines = []
+    required = False
+
+    for line in rendered_config.split('\n'):
+        # Upon encountering a required configuration option, skip commenting out lines until the
+        # next blank line.
+        stripped_line = line.strip()
+        if stripped_line in {'source_directories:', 'repositories:'} or line == 'location:':
+            required = True
+        elif not stripped_line:
+            required = False
+
+        lines.append(_comment_out_line(line) if not required else line)
+
+    return '\n'.join(lines)
+
+
+def _render_configuration(config):
+    '''
+    Given a config data structure of nested OrderedDicts, render the config as YAML and return it.
+    '''
+    return yaml.round_trip_dump(config, indent=INDENT, block_seq_indent=INDENT)
+
+
+def write_configuration(config_filename, rendered_config, mode=0o600):
+    '''
+    Given a target config filename and rendered config YAML, write it out to file. Create any
+    containing directories as needed.
     '''
     '''
     if os.path.exists(config_filename):
     if os.path.exists(config_filename):
         raise FileExistsError('{} already exists. Aborting.'.format(config_filename))
         raise FileExistsError('{} already exists. Aborting.'.format(config_filename))
@@ -54,7 +102,7 @@ def write_configuration(config_filename, config, mode=0o600):
         pass
         pass
 
 
     with open(config_filename, 'w') as config_file:
     with open(config_filename, 'w') as config_file:
-        config_file.write(yaml.round_trip_dump(config, indent=INDENT, block_seq_indent=INDENT))
+        config_file.write(rendered_config)
 
 
     os.chmod(config_filename, mode)
     os.chmod(config_filename, mode)
 
 
@@ -90,4 +138,7 @@ def generate_sample_configuration(config_filename, schema_filename):
     schema = yaml.round_trip_load(open(schema_filename))
     schema = yaml.round_trip_load(open(schema_filename))
     config = _schema_to_sample_configuration(schema)
     config = _schema_to_sample_configuration(schema)
 
 
-    write_configuration(config_filename, config)
+    write_configuration(
+        config_filename,
+        _comment_out_optional_configuration(_render_configuration(config))
+    )

+ 10 - 10
borgmatic/config/schema.yaml

@@ -18,6 +18,16 @@ map:
                     - /home
                     - /home
                     - /etc
                     - /etc
                     - /var/log/syslog*
                     - /var/log/syslog*
+            repositories:
+                required: true
+                seq:
+                    - type: scalar
+                desc: |
+                    Paths to local or remote repositories (required). Tildes are expanded. Multiple
+                    repositories are backed up to in sequence. See ssh_command for SSH options like
+                    identity file or port.
+                example:
+                    - user@backupserver:sourcehostname.borg
             one_file_system:
             one_file_system:
                 type: bool
                 type: bool
                 desc: Stay in same file system (do not cross mount points).
                 desc: Stay in same file system (do not cross mount points).
@@ -48,16 +58,6 @@ map:
                 type: scalar
                 type: scalar
                 desc: Alternate Borg remote executable. Defaults to "borg".
                 desc: Alternate Borg remote executable. Defaults to "borg".
                 example: borg1
                 example: borg1
-            repositories:
-                required: true
-                seq:
-                    - type: scalar
-                desc: |
-                    Paths to local or remote repositories (required). Tildes are expanded. Multiple
-                    repositories are backed up to in sequence. See ssh_command for SSH options like
-                    identity file or port.
-                example:
-                    - user@backupserver:sourcehostname.borg
             patterns:
             patterns:
                 seq:
                 seq:
                     - type: scalar
                     - type: scalar

+ 69 - 4
borgmatic/tests/integration/config/test_generate.py

@@ -16,6 +16,69 @@ def test_insert_newline_before_comment_does_not_raise():
     module._insert_newline_before_comment(config, field_name)
     module._insert_newline_before_comment(config, field_name)
 
 
 
 
+def test_comment_out_line_skips_blank_line():
+    line = '    \n'
+
+    assert module._comment_out_line(line) == line
+
+
+def test_comment_out_line_skips_already_commented_out_line():
+    line = '    # foo'
+
+    assert module._comment_out_line(line) == line
+
+
+def test_comment_out_line_comments_section_name():
+    line = 'figgy-pudding:'
+
+    assert module._comment_out_line(line) == '#' + line
+
+
+def test_comment_out_line_comments_indented_option():
+    line = '    enabled: true'
+
+    assert module._comment_out_line(line) == '    #enabled: true'
+
+
+def test_comment_out_optional_configuration_comments_optional_config_only():
+    flexmock(module)._comment_out_line = lambda line: '#' + line
+    config = '''
+foo:
+    bar:
+        - baz
+        - quux
+
+location:
+    repositories:
+        - one
+        - two
+
+    other: thing
+    '''
+
+    expected_config = '''
+#foo:
+#    bar:
+#        - baz
+#        - quux
+#
+location:
+    repositories:
+        - one
+        - two
+#
+#    other: thing
+    '''
+
+    assert module._comment_out_optional_configuration(config.strip()) == expected_config.strip()
+
+
+def test_render_configuration_does_not_raise():
+    flexmock(module.yaml).should_receive('round_trip_dump')
+
+    module._render_configuration({})
+
+
 def test_write_configuration_does_not_raise():
 def test_write_configuration_does_not_raise():
     flexmock(os.path).should_receive('exists').and_return(False)
     flexmock(os.path).should_receive('exists').and_return(False)
     flexmock(os).should_receive('makedirs')
     flexmock(os).should_receive('makedirs')
@@ -23,14 +86,14 @@ def test_write_configuration_does_not_raise():
     builtins.should_receive('open').and_return(StringIO())
     builtins.should_receive('open').and_return(StringIO())
     flexmock(os).should_receive('chmod')
     flexmock(os).should_receive('chmod')
 
 
-    module.write_configuration('config.yaml', {})
+    module.write_configuration('config.yaml', 'config: yaml')
 
 
 
 
 def test_write_configuration_with_already_existing_file_raises():
 def test_write_configuration_with_already_existing_file_raises():
     flexmock(os.path).should_receive('exists').and_return(True)
     flexmock(os.path).should_receive('exists').and_return(True)
 
 
     with pytest.raises(FileExistsError):
     with pytest.raises(FileExistsError):
-        module.write_configuration('config.yaml', {})
+        module.write_configuration('config.yaml', 'config: yaml')
 
 
 
 
 def test_write_configuration_with_already_existing_directory_does_not_raise():
 def test_write_configuration_with_already_existing_directory_does_not_raise():
@@ -40,7 +103,7 @@ def test_write_configuration_with_already_existing_directory_does_not_raise():
     builtins.should_receive('open').and_return(StringIO())
     builtins.should_receive('open').and_return(StringIO())
     flexmock(os).should_receive('chmod')
     flexmock(os).should_receive('chmod')
 
 
-    module.write_configuration('config.yaml', {})
+    module.write_configuration('config.yaml', 'config: yaml')
 
 
 
 
 def test_add_comments_to_configuration_does_not_raise():
 def test_add_comments_to_configuration_does_not_raise():
@@ -59,7 +122,9 @@ def test_add_comments_to_configuration_does_not_raise():
 def test_generate_sample_configuration_does_not_raise():
 def test_generate_sample_configuration_does_not_raise():
     builtins = flexmock(sys.modules['builtins'])
     builtins = flexmock(sys.modules['builtins'])
     builtins.should_receive('open').with_args('schema.yaml').and_return('')
     builtins.should_receive('open').with_args('schema.yaml').and_return('')
-    flexmock(module).should_receive('write_configuration')
     flexmock(module).should_receive('_schema_to_sample_configuration')
     flexmock(module).should_receive('_schema_to_sample_configuration')
+    flexmock(module).should_receive('_render_configuration')
+    flexmock(module).should_receive('_comment_out_optional_configuration')
+    flexmock(module).should_receive('write_configuration')
 
 
     module.generate_sample_configuration('config.yaml', 'schema.yaml')
     module.generate_sample_configuration('config.yaml', 'schema.yaml')