Browse Source

Support for Borg repository initialization via borgmatic --init command-line flag (#110).

tags/1.2.12
Dan Helfman 6 months ago
parent
commit
cc9dbb1def

+ 4
- 2
NEWS View File

@@ -1,7 +1,9 @@
1
-1.2.12.dev0
1
+1.2.12
2
+ * #110: Support for Borg repository initialization via borgmatic --init command-line flag.
2 3
  * #111: Update Borg create --filter values so a dry run lists files to back up.
3 4
  * #113: Update README with link to a new/forked Docker image.
4
- * Error when deprecated --excludes command-line option is used.
5
+ * Prevent deprecated --excludes command-line option from being used.
6
+ * Refactor README a bit to flow better for first-time users.
5 7
 
6 8
 1.2.11
7 9
  * #108: Support for Borg create --progress via borgmatic command-line flag.

+ 159
- 128
README.md View File

@@ -58,28 +58,9 @@ href="https://asciinema.org/a/203761" target="_blank">screencast</a>.
58 58
 
59 59
 To get up and running, first [install
60 60
 Borg](https://borgbackup.readthedocs.io/en/latest/installation.html), at
61
-least version 1.1. Then, follow the [Borg Quick
62
-Start](https://borgbackup.readthedocs.org/en/latest/quickstart.html) to create
63
-a repository on a local or remote host.
61
+least version 1.1.
64 62
 
65
-Note that if you plan to run borgmatic on a schedule with cron, and you
66
-encrypt your Borg repository with a passphrase instead of a key file, you'll
67
-either need to set the borgmatic `encryption_passphrase` configuration
68
-variable or set the `BORG_PASSPHRASE` environment variable. See the
69
-[repository encryption
70
-section](https://borgbackup.readthedocs.io/en/latest/quickstart.html#repository-encryption)
71
-of the Quick Start for more info.
72
-
73
-Alternatively, the passphrase can be specified programatically by setting
74
-either the borgmatic `encryption_passcommand` configuration variable or the
75
-`BORG_PASSCOMMAND` environment variable. See the [Borg Security
76
-FAQ](http://borgbackup.readthedocs.io/en/stable/faq.html#how-can-i-specify-the-encryption-passphrase-programmatically)
77
-for more info.
78
-
79
-If the repository is on a remote host, make sure that your local root user has
80
-key-based ssh access to the desired user account on the remote host.
81
-
82
-To install borgmatic, run the following command to download and install it:
63
+Then, run the following command to download and install borgmatic:
83 64
 
84 65
 ```bash
85 66
 sudo pip3 install --upgrade borgmatic
@@ -88,6 +69,7 @@ sudo pip3 install --upgrade borgmatic
88 69
 Note that your pip binary may have a different name than "pip3". Make sure
89 70
 you're using Python 3, as borgmatic does not support Python 2.
90 71
 
72
+
91 73
 ### Other ways to install
92 74
 
93 75
  * [A borgmatic Docker image](https://hub.docker.com/r/monachus/borgmatic/) based
@@ -101,6 +83,7 @@ you're using Python 3, as borgmatic does not support Python 2.
101 83
  * [A borgmatic package for OpenBSD](http://ports.su/sysutils/borgmatic).
102 84
 <br><br>
103 85
 
86
+
104 87
 ## Configuration
105 88
 
106 89
 After you install borgmatic, generate a sample configuration file:
@@ -124,6 +107,161 @@ borgmatic has added new options since you originally created your
124 107
 configuration file.
125 108
 
126 109
 
110
+### Encryption
111
+
112
+Note that if you plan to run borgmatic on a schedule with cron, and you
113
+encrypt your Borg repository with a passphrase instead of a key file, you'll
114
+either need to set the borgmatic `encryption_passphrase` configuration
115
+variable or set the `BORG_PASSPHRASE` environment variable. See the
116
+[repository encryption
117
+section](https://borgbackup.readthedocs.io/en/latest/quickstart.html#repository-encryption)
118
+of the Quick Start for more info.
119
+
120
+Alternatively, the passphrase can be specified programatically by setting
121
+either the borgmatic `encryption_passcommand` configuration variable or the
122
+`BORG_PASSCOMMAND` environment variable. See the [Borg Security
123
+FAQ](http://borgbackup.readthedocs.io/en/stable/faq.html#how-can-i-specify-the-encryption-passphrase-programmatically)
124
+for more info.
125
+
126
+
127
+## Usage
128
+
129
+### Initialization
130
+
131
+Before you can create backups with borgmatic, you first need to initialize a
132
+Borg repository so you have a destination for your backup archives. (But skip
133
+this step if you already have a Borg repository.) To create a repository, run
134
+a command like the following:
135
+
136
+```bash
137
+borgmatic --init --encryption repokey
138
+```
139
+
140
+This uses the borgmatic configuration file you created above to determine
141
+which local or remote repository to create, and encrypts it with the
142
+encryption passphrase specified there if one is provided. Read about [Borg
143
+encryption
144
+modes](https://borgbackup.readthedocs.io/en/latest/usage/init.html#encryption-modes)
145
+for the menu of available encryption modes.
146
+
147
+Also, optionally check out the [Borg Quick
148
+Start](https://borgbackup.readthedocs.org/en/latest/quickstart.html) for more
149
+background about repository initialization.
150
+
151
+If the repository is on a remote host, make sure that your local user has
152
+key-based SSH access to the desired user account on the remote host.
153
+
154
+
155
+### Backups
156
+
157
+You can run borgmatic and start a backup simply by invoking it without
158
+arguments:
159
+
160
+```bash
161
+borgmatic
162
+```
163
+
164
+This will also prune any old backups as per the configured retention policy,
165
+and check backups for consistency problems due to things like file damage.
166
+
167
+If you'd like to see the available command-line arguments, view the help:
168
+
169
+```bash
170
+borgmatic --help
171
+```
172
+
173
+Note that borgmatic prunes archives *before* creating an archive, so as to
174
+free up space for archiving. This means that when a borgmatic run finishes,
175
+there may still be prune-able archives. Not to worry, as they will get cleaned
176
+up at the start of the next run.
177
+
178
+
179
+### Verbosity
180
+
181
+By default, the backup will proceed silently except in the case of errors. But
182
+if you'd like to to get additional information about the progress of the
183
+backup as it proceeds, use the verbosity option:
184
+
185
+```bash
186
+borgmatic --verbosity 1
187
+```
188
+
189
+Or, for even more progress spew:
190
+
191
+```bash
192
+borgmatic --verbosity 2
193
+```
194
+
195
+### À la carte
196
+
197
+If you want to run borgmatic with only pruning, creating, or checking enabled,
198
+the following optional flags are available:
199
+
200
+```bash
201
+borgmatic --prune
202
+borgmatic --create
203
+borgmatic --check
204
+```
205
+
206
+You can run with only one of these flags provided, or you can mix and match
207
+any number of them. This supports use cases like running consistency checks
208
+from a different cron job with a different frequency, or running pruning with
209
+a different verbosity level.
210
+
211
+Additionally, borgmatic provides convenient flags for Borg's
212
+[list](https://borgbackup.readthedocs.io/en/stable/usage/list.html) and
213
+[info](https://borgbackup.readthedocs.io/en/stable/usage/info.html)
214
+functionality:
215
+
216
+
217
+```bash
218
+borgmatic --list
219
+borgmatic --info
220
+```
221
+
222
+You can include an optional `--json` flag with `--create`, `--list`, or
223
+`--info` to get the output formatted as JSON.
224
+
225
+
226
+## Autopilot
227
+
228
+If you want to run borgmatic automatically, say once a day, the you can
229
+configure a job runner to invoke it periodically.
230
+
231
+### cron
232
+
233
+If you're using cron, download the [sample cron
234
+file](https://projects.torsion.org/witten/borgmatic/src/master/sample/cron/borgmatic).
235
+Then, from the directory where you downloaded it:
236
+
237
+```bash
238
+sudo mv borgmatic /etc/cron.d/borgmatic
239
+sudo chmod +x /etc/cron.d/borgmatic
240
+```
241
+
242
+You can modify the cron file if you'd like to run borgmatic more or less frequently.
243
+
244
+### systemd
245
+
246
+If you're using systemd instead of cron to run jobs, download the [sample
247
+systemd service
248
+file](https://projects.torsion.org/witten/borgmatic/src/master/sample/systemd/borgmatic.service)
249
+and the [sample systemd timer
250
+file](https://projects.torsion.org/witten/borgmatic/src/master/sample/systemd/borgmatic.timer).
251
+Then, from the directory where you downloaded them:
252
+
253
+```bash
254
+sudo mv borgmatic.service borgmatic.timer /etc/systemd/system/
255
+sudo systemctl enable borgmatic.timer
256
+sudo systemctl start borgmatic.timer
257
+```
258
+
259
+Feel free to modify the timer file based on how frequently you'd like
260
+borgmatic to run.
261
+
262
+
263
+## Advanced configuration
264
+
127 265
 ### Multiple configuration files
128 266
 
129 267
 A more advanced usage is to create multiple separate configuration files and
@@ -247,113 +385,6 @@ That's it! borgmatic will continue using your /etc/borgmatic configuration
247 385
 files.
248 386
 
249 387
 
250
-## Usage
251
-
252
-You can run borgmatic and start a backup simply by invoking it without
253
-arguments:
254
-
255
-```bash
256
-borgmatic
257
-```
258
-
259
-This will also prune any old backups as per the configured retention policy,
260
-and check backups for consistency problems due to things like file damage.
261
-
262
-If you'd like to see the available command-line arguments, view the help:
263
-
264
-```bash
265
-borgmatic --help
266
-```
267
-
268
-Note that borgmatic prunes archives *before* creating an archive, so as to
269
-free up space for archiving. This means that when a borgmatic run finishes,
270
-there may still be prune-able archives. Not to worry, as they will get cleaned
271
-up at the start of the next run.
272
-
273
-### Verbosity
274
-
275
-By default, the backup will proceed silently except in the case of errors. But
276
-if you'd like to to get additional information about the progress of the
277
-backup as it proceeds, use the verbosity option:
278
-
279
-```bash
280
-borgmatic --verbosity 1
281
-```
282
-
283
-Or, for even more progress spew:
284
-
285
-```bash
286
-borgmatic --verbosity 2
287
-```
288
-
289
-### À la carte
290
-
291
-If you want to run borgmatic with only pruning, creating, or checking enabled,
292
-the following optional flags are available:
293
-
294
-```bash
295
-borgmatic --prune
296
-borgmatic --create
297
-borgmatic --check
298
-```
299
-
300
-You can run with only one of these flags provided, or you can mix and match
301
-any number of them. This supports use cases like running consistency checks
302
-from a different cron job with a different frequency, or running pruning with
303
-a different verbosity level.
304
-
305
-Additionally, borgmatic provides convenient flags for Borg's
306
-[list](https://borgbackup.readthedocs.io/en/stable/usage/list.html) and
307
-[info](https://borgbackup.readthedocs.io/en/stable/usage/info.html)
308
-functionality:
309
-
310
-
311
-```bash
312
-borgmatic --list
313
-borgmatic --info
314
-```
315
-
316
-You can include an optional `--json` flag with `--create`, `--list`, or
317
-`--info` to get the output formatted as JSON.
318
-
319
-
320
-## Autopilot
321
-
322
-If you want to run borgmatic automatically, say once a day, the you can
323
-configure a job runner to invoke it periodically.
324
-
325
-### cron
326
-
327
-If you're using cron, download the [sample cron
328
-file](https://projects.torsion.org/witten/borgmatic/src/master/sample/cron/borgmatic).
329
-Then, from the directory where you downloaded it:
330
-
331
-```bash
332
-sudo mv borgmatic /etc/cron.d/borgmatic
333
-sudo chmod +x /etc/cron.d/borgmatic
334
-```
335
-
336
-You can modify the cron file if you'd like to run borgmatic more or less frequently.
337
-
338
-### systemd
339
-
340
-If you're using systemd instead of cron to run jobs, download the [sample
341
-systemd service
342
-file](https://projects.torsion.org/witten/borgmatic/src/master/sample/systemd/borgmatic.service)
343
-and the [sample systemd timer
344
-file](https://projects.torsion.org/witten/borgmatic/src/master/sample/systemd/borgmatic.timer).
345
-Then, from the directory where you downloaded them:
346
-
347
-```bash
348
-sudo mv borgmatic.service borgmatic.timer /etc/systemd/system/
349
-sudo systemctl enable borgmatic.timer
350
-sudo systemctl start borgmatic.timer
351
-```
352
-
353
-Feel free to modify the timer file based on how frequently you'd like
354
-borgmatic to run.
355
-
356
-
357 388
 ## Support and contributing
358 389
 
359 390
 ### Issues

+ 0
- 14
borgmatic/borg/create.py View File

@@ -9,20 +9,6 @@ import tempfile
9 9
 logger = logging.getLogger(__name__)
10 10
 
11 11
 
12
-def initialize_environment(storage_config):
13
-    passcommand = storage_config.get('encryption_passcommand')
14
-    if passcommand:
15
-        os.environ['BORG_PASSCOMMAND'] = passcommand
16
-
17
-    passphrase = storage_config.get('encryption_passphrase')
18
-    if passphrase:
19
-        os.environ['BORG_PASSPHRASE'] = passphrase
20
-
21
-    ssh_command = storage_config.get('ssh_command')
22
-    if ssh_command:
23
-        os.environ['BORG_RSH'] = ssh_command
24
-
25
-
26 12
 def _expand_directory(directory):
27 13
     '''
28 14
     Given a directory path, expand any tilde (representing a user's home directory) and any globs

+ 15
- 0
borgmatic/borg/environment.py View File

@@ -0,0 +1,15 @@
1
+import os
2
+
3
+
4
+def initialize(storage_config):
5
+    passcommand = storage_config.get('encryption_passcommand')
6
+    if passcommand:
7
+        os.environ['BORG_PASSCOMMAND'] = passcommand
8
+
9
+    passphrase = storage_config.get('encryption_passphrase')
10
+    if passphrase:
11
+        os.environ['BORG_PASSPHRASE'] = passphrase
12
+
13
+    ssh_command = storage_config.get('ssh_command')
14
+    if ssh_command:
15
+        os.environ['BORG_RSH'] = ssh_command

+ 31
- 0
borgmatic/borg/init.py View File

@@ -0,0 +1,31 @@
1
+import logging
2
+import subprocess
3
+
4
+
5
+logger = logging.getLogger(__name__)
6
+
7
+
8
+def initialize_repository(
9
+    repository,
10
+    encryption_mode,
11
+    append_only=None,
12
+    storage_quota=None,
13
+    local_path='borg',
14
+    remote_path=None,
15
+):
16
+    '''
17
+    Given a local or remote repository path, a Borg encryption mode, whether the repository should
18
+    be append-only, and the storage quota to use, initialize the repository.
19
+    '''
20
+    full_command = (
21
+        (local_path, 'init', repository)
22
+        + (('--encryption', encryption_mode) if encryption_mode else ())
23
+        + (('--append-only',) if append_only else ())
24
+        + (('--storage-quota', storage_quota) if storage_quota else ())
25
+        + (('--info',) if logger.getEffectiveLevel() == logging.INFO else ())
26
+        + (('--debug',) if logger.isEnabledFor(logging.DEBUG) else ())
27
+        + (('--remote-path', remote_path) if remote_path else ())
28
+    )
29
+
30
+    logger.debug(' '.join(full_command))
31
+    subprocess.check_call(full_command)

+ 49
- 3
borgmatic/commands/borgmatic.py View File

@@ -8,9 +8,11 @@ import sys
8 8
 from borgmatic.borg import (
9 9
     check as borg_check,
10 10
     create as borg_create,
11
+    environment as borg_environment,
11 12
     prune as borg_prune,
12 13
     list as borg_list,
13 14
     info as borg_info,
15
+    init as borg_init,
14 16
 )
15 17
 from borgmatic.commands import hook
16 18
 from borgmatic.config import checks, collect, convert, validate
@@ -53,6 +55,26 @@ def parse_arguments(*arguments):
53 55
         dest='excludes_filename',
54 56
         help='Deprecated in favor of exclude_patterns within configuration',
55 57
     )
58
+    parser.add_argument(
59
+        '-I', '--init', dest='init', action='store_true', help='Initialize an empty Borg repository'
60
+    )
61
+    parser.add_argument(
62
+        '-e',
63
+        '--encryption',
64
+        dest='encryption_mode',
65
+        help='Borg repository encryption mode (for use with --init)',
66
+    )
67
+    parser.add_argument(
68
+        '--append-only',
69
+        dest='append_only',
70
+        action='store_true',
71
+        help='Create an append-only repository (for use with --init)',
72
+    )
73
+    parser.add_argument(
74
+        '--storage-quota',
75
+        dest='storage_quota',
76
+        help='Create a repository with a fixed storage quota (for use with --init)',
77
+    )
56 78
     parser.add_argument(
57 79
         '-p',
58 80
         '--prune',
@@ -111,7 +133,21 @@ def parse_arguments(*arguments):
111 133
     args = parser.parse_args(arguments)
112 134
 
113 135
     if args.excludes_filename:
114
-        raise ValueError('The --excludes option has been replaced with exclude_patterns in configuration')
136
+        raise ValueError(
137
+            'The --excludes option has been replaced with exclude_patterns in configuration'
138
+        )
139
+
140
+    if (args.encryption_mode or args.append_only or args.storage_quota) and not args.init:
141
+        raise ValueError(
142
+            'The --encryption, --append-only, and --storage-quota options can only be used with the --init option'
143
+        )
144
+
145
+    if args.init and (args.prune or args.create or args.dry_run):
146
+        raise ValueError(
147
+            'The --init option cannot be used with the --prune, --create, or --dry-run options'
148
+        )
149
+    if args.init and not args.encryption_mode:
150
+        raise ValueError('The --encryption option is required with the --init option')
115 151
 
116 152
     if args.progress and not args.create:
117 153
         raise ValueError('The --progress option can only be used with the --create option')
@@ -128,7 +164,7 @@ def parse_arguments(*arguments):
128 164
 
129 165
     # If any of the action flags are explicitly requested, leave them as-is. Otherwise, assume
130 166
     # defaults: Mutate the given arguments to enable the default actions.
131
-    if args.prune or args.create or args.check or args.list or args.info:
167
+    if args.init or args.prune or args.create or args.check or args.list or args.info:
132 168
         return args
133 169
 
134 170
     args.prune = True
@@ -152,7 +188,7 @@ def run_configuration(config_filename, args):  # pragma: no cover
152 188
     try:
153 189
         local_path = location.get('local_path', 'borg')
154 190
         remote_path = location.get('remote_path')
155
-        borg_create.initialize_environment(storage)
191
+        borg_environment.initialize(storage)
156 192
 
157 193
         if args.create:
158 194
             hook.execute_hook(hooks.get('before_backup'), config_filename, 'pre-backup')
@@ -206,6 +242,16 @@ def _run_commands_on_repository(
206 242
 ):  # pragma: no cover
207 243
     repository = os.path.expanduser(unexpanded_repository)
208 244
     dry_run_label = ' (dry run; not making any changes)' if args.dry_run else ''
245
+    if args.init:
246
+        logger.info('{}: Initializing repository'.format(repository))
247
+        borg_init.initialize_repository(
248
+            repository,
249
+            args.encryption_mode,
250
+            args.append_only,
251
+            args.storage_quota,
252
+            local_path=local_path,
253
+            remote_path=remote_path,
254
+        )
209 255
     if args.prune:
210 256
         logger.info('{}: Pruning archives{}'.format(repository, dry_run_label))
211 257
         borg_prune.prune_archives(

+ 1
- 1
setup.py View File

@@ -1,7 +1,7 @@
1 1
 from setuptools import setup, find_packages
2 2
 
3 3
 
4
-VERSION = '1.2.12.dev0'
4
+VERSION = '1.2.12'
5 5
 
6 6
 
7 7
 setup(

+ 7
- 6
tests/end-to-end/test_borgmatic.py View File

@@ -9,7 +9,8 @@ import tempfile
9 9
 def generate_configuration(config_path, repository_path):
10 10
     '''
11 11
     Generate borgmatic configuration into a file at the config path, and update the defaults so as
12
-    to work for testing (including injecting the given repository path).
12
+    to work for testing (including injecting the given repository path and tacking on an encryption
13
+    passphrase).
13 14
     '''
14 15
     subprocess.check_call(
15 16
         'generate-borgmatic-config --destination {}'.format(config_path).split(' ')
@@ -21,6 +22,7 @@ def generate_configuration(config_path, repository_path):
21 22
         .replace('- /home', '- {}'.format(config_path))
22 23
         .replace('- /etc', '')
23 24
         .replace('- /var/log/syslog*', '')
25
+        + 'storage:\n    encryption_passphrase: "test"'
24 26
     )
25 27
     config_file = open(config_path, 'w')
26 28
     config_file.write(config)
@@ -33,14 +35,13 @@ def test_borgmatic_command():
33 35
     repository_path = os.path.join(temporary_directory, 'test.borg')
34 36
 
35 37
     try:
36
-        subprocess.check_call(
37
-            'borg init --encryption repokey {}'.format(repository_path).split(' '),
38
-            env={'BORG_PASSPHRASE': '', **os.environ},
39
-        )
40
-
41 38
         config_path = os.path.join(temporary_directory, 'test.yaml')
42 39
         generate_configuration(config_path, repository_path)
43 40
 
41
+        subprocess.check_call(
42
+            'borgmatic -v 2 --config {} --init --encryption repokey'.format(config_path).split(' ')
43
+        )
44
+
44 45
         # Run borgmatic to generate a backup archive, and then list it to make sure it exists.
45 46
         subprocess.check_call('borgmatic --config {}'.format(config_path).split(' '))
46 47
         output = subprocess.check_output(

+ 63
- 14
tests/integration/commands/test_borgmatic.py View File

@@ -16,13 +16,6 @@ def test_parse_arguments_with_no_arguments_uses_defaults():
16 16
     assert parser.json is False
17 17
 
18 18
 
19
-def test_parse_arguments_disallows_deprecated_excludes_option():
20
-    flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
21
-
22
-    with pytest.raises(ValueError):
23
-        module.parse_arguments('--config', 'myconfig', '--excludes', 'myexcludes')
24
-
25
-
26 19
 def test_parse_arguments_with_multiple_config_paths_parses_as_list():
27 20
     flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
28 21
 
@@ -32,7 +25,7 @@ def test_parse_arguments_with_multiple_config_paths_parses_as_list():
32 25
     assert parser.verbosity is 0
33 26
 
34 27
 
35
-def test_parse_arguments_with_verbosity_flag_overrides_default():
28
+def test_parse_arguments_with_verbosity_overrides_default():
36 29
     config_paths = ['default']
37 30
     flexmock(module.collect).should_receive('get_default_config_paths').and_return(config_paths)
38 31
 
@@ -43,7 +36,7 @@ def test_parse_arguments_with_verbosity_flag_overrides_default():
43 36
     assert parser.verbosity == 1
44 37
 
45 38
 
46
-def test_parse_arguments_with_json_flag_overrides_default():
39
+def test_parse_arguments_with_json_overrides_default():
47 40
     parser = module.parse_arguments('--list', '--json')
48 41
     assert parser.json is True
49 42
 
@@ -85,25 +78,81 @@ def test_parse_arguments_with_invalid_arguments_exits():
85 78
         module.parse_arguments('--posix-me-harder')
86 79
 
87 80
 
88
-def test_parse_arguments_with_progress_and_create_flags_does_not_raise():
81
+def test_parse_arguments_disallows_deprecated_excludes_option():
82
+    flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
83
+
84
+    with pytest.raises(ValueError):
85
+        module.parse_arguments('--config', 'myconfig', '--excludes', 'myexcludes')
86
+
87
+
88
+def test_parse_arguments_disallows_encryption_mode_without_init():
89
+    flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
90
+
91
+    with pytest.raises(ValueError):
92
+        module.parse_arguments('--config', 'myconfig', '--encryption', 'repokey')
93
+
94
+
95
+def test_parse_arguments_requires_encryption_mode_with_init():
96
+    flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
97
+
98
+    with pytest.raises(ValueError):
99
+        module.parse_arguments('--config', 'myconfig', '--init')
100
+
101
+
102
+def test_parse_arguments_disallows_append_only_without_init():
103
+    flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
104
+
105
+    with pytest.raises(ValueError):
106
+        module.parse_arguments('--config', 'myconfig', '--append-only')
107
+
108
+
109
+def test_parse_arguments_disallows_storage_quota_without_init():
110
+    flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
111
+
112
+    with pytest.raises(ValueError):
113
+        module.parse_arguments('--config', 'myconfig', '--storage-quota', '5G')
114
+
115
+
116
+def test_parse_arguments_disallows_init_and_prune():
117
+    flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
118
+
119
+    with pytest.raises(ValueError):
120
+        module.parse_arguments('--config', 'myconfig', '--init', '--prune')
121
+
122
+
123
+def test_parse_arguments_disallows_init_and_create():
124
+    flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
125
+
126
+    with pytest.raises(ValueError):
127
+        module.parse_arguments('--config', 'myconfig', '--init', '--create')
128
+
129
+
130
+def test_parse_arguments_disallows_init_and_dry_run():
131
+    flexmock(module.collect).should_receive('get_default_config_paths').and_return(['default'])
132
+
133
+    with pytest.raises(ValueError):
134
+        module.parse_arguments('--config', 'myconfig', '--init', '--dry-run')
135
+
136
+
137
+def test_parse_arguments_allows_progress_and_create():
89 138
     module.parse_arguments('--progress', '--create', '--list')
90 139
 
91 140
 
92
-def test_parse_arguments_with_progress_flag_but_no_create_flag_raises_value_error():
141
+def test_parse_arguments_disallows_progress_without_create():
93 142
     with pytest.raises(ValueError):
94 143
         module.parse_arguments('--progress', '--list')
95 144
 
96 145
 
97
-def test_parse_arguments_with_json_flag_with_list_or_info_flag_does_not_raise_any_error():
146
+def test_parse_arguments_allows_json_with_list_or_info():
98 147
     module.parse_arguments('--list', '--json')
99 148
     module.parse_arguments('--info', '--json')
100 149
 
101 150
 
102
-def test_parse_arguments_with_json_flag_but_no_list_or_info_flag_raises_value_error():
151
+def test_parse_arguments_disallows_json_without_list_or_info():
103 152
     with pytest.raises(ValueError):
104 153
         module.parse_arguments('--json')
105 154
 
106 155
 
107
-def test_parse_arguments_with_json_flag_and_both_list_and_info_flag_raises_value_error():
156
+def test_parse_arguments_disallows_json_with_both_list_and_info():
108 157
     with pytest.raises(ValueError):
109 158
         module.parse_arguments('--list', '--info', '--json')

+ 0
- 47
tests/unit/borg/test_create.py View File

@@ -1,5 +1,4 @@
1 1
 import logging
2
-import os
3 2
 
4 3
 from flexmock import flexmock
5 4
 
@@ -7,52 +6,6 @@ from borgmatic.borg import create as module
7 6
 from ..test_verbosity import insert_logging_mock
8 7
 
9 8
 
10
-def test_initialize_environment_with_passcommand_should_set_environment():
11
-    orig_environ = os.environ
12
-
13
-    try:
14
-        os.environ = {}
15
-        module.initialize_environment({'encryption_passcommand': 'command'})
16
-        assert os.environ.get('BORG_PASSCOMMAND') == 'command'
17
-    finally:
18
-        os.environ = orig_environ
19
-
20
-
21
-def test_initialize_environment_with_passphrase_should_set_environment():
22
-    orig_environ = os.environ
23
-
24
-    try:
25
-        os.environ = {}
26
-        module.initialize_environment({'encryption_passphrase': 'pass'})
27
-        assert os.environ.get('BORG_PASSPHRASE') == 'pass'
28
-    finally:
29
-        os.environ = orig_environ
30
-
31
-
32
-def test_initialize_environment_with_ssh_command_should_set_environment():
33
-    orig_environ = os.environ
34
-
35
-    try:
36
-        os.environ = {}
37
-        module.initialize_environment({'ssh_command': 'ssh -C'})
38
-        assert os.environ.get('BORG_RSH') == 'ssh -C'
39
-    finally:
40
-        os.environ = orig_environ
41
-
42
-
43
-def test_initialize_environment_without_configuration_should_not_set_environment():
44
-    orig_environ = os.environ
45
-
46
-    try:
47
-        os.environ = {}
48
-        module.initialize_environment({})
49
-        assert os.environ.get('BORG_PASSCOMMAND') is None
50
-        assert os.environ.get('BORG_PASSPHRASE') is None
51
-        assert os.environ.get('BORG_RSH') is None
52
-    finally:
53
-        os.environ = orig_environ
54
-
55
-
56 9
 def test_expand_directory_with_basic_path_passes_it_through():
57 10
     flexmock(module.os.path).should_receive('expanduser').and_return('foo')
58 11
     flexmock(module.glob).should_receive('glob').and_return([])

+ 49
- 0
tests/unit/borg/test_environment.py View File

@@ -0,0 +1,49 @@
1
+import os
2
+
3
+from borgmatic.borg import environment as module
4
+
5
+
6
+def test_initialize_with_passcommand_should_set_environment():
7
+    orig_environ = os.environ
8
+
9
+    try:
10
+        os.environ = {}
11
+        module.initialize({'encryption_passcommand': 'command'})
12
+        assert os.environ.get('BORG_PASSCOMMAND') == 'command'
13
+    finally:
14
+        os.environ = orig_environ
15
+
16
+
17
+def test_initialize_with_passphrase_should_set_environment():
18
+    orig_environ = os.environ
19
+
20
+    try:
21
+        os.environ = {}
22
+        module.initialize({'encryption_passphrase': 'pass'})
23
+        assert os.environ.get('BORG_PASSPHRASE') == 'pass'
24
+    finally:
25
+        os.environ = orig_environ
26
+
27
+
28
+def test_initialize_with_ssh_command_should_set_environment():
29
+    orig_environ = os.environ
30
+
31
+    try:
32
+        os.environ = {}
33
+        module.initialize({'ssh_command': 'ssh -C'})
34
+        assert os.environ.get('BORG_RSH') == 'ssh -C'
35
+    finally:
36
+        os.environ = orig_environ
37
+
38
+
39
+def test_initialize_without_configuration_should_not_set_environment():
40
+    orig_environ = os.environ
41
+
42
+    try:
43
+        os.environ = {}
44
+        module.initialize({})
45
+        assert os.environ.get('BORG_PASSCOMMAND') is None
46
+        assert os.environ.get('BORG_PASSPHRASE') is None
47
+        assert os.environ.get('BORG_RSH') is None
48
+    finally:
49
+        os.environ = orig_environ

+ 58
- 0
tests/unit/borg/test_init.py View File

@@ -0,0 +1,58 @@
1
+import logging
2
+
3
+from flexmock import flexmock
4
+
5
+from borgmatic.borg import init as module
6
+from ..test_verbosity import insert_logging_mock
7
+
8
+
9
+def insert_subprocess_mock(check_call_command, **kwargs):
10
+    subprocess = flexmock(module.subprocess)
11
+    subprocess.should_receive('check_call').with_args(check_call_command, **kwargs).once()
12
+
13
+
14
+INIT_COMMAND = ('borg', 'init', 'repo', '--encryption', 'repokey')
15
+
16
+
17
+def test_initialize_repository_calls_borg_with_parameters():
18
+    insert_subprocess_mock(INIT_COMMAND)
19
+
20
+    module.initialize_repository(repository='repo', encryption_mode='repokey')
21
+
22
+
23
+def test_initialize_repository_with_append_only_calls_borg_with_append_only_parameter():
24
+    insert_subprocess_mock(INIT_COMMAND + ('--append-only',))
25
+
26
+    module.initialize_repository(repository='repo', encryption_mode='repokey', append_only=True)
27
+
28
+
29
+def test_initialize_repository_with_storage_quota_calls_borg_with_storage_quota_parameter():
30
+    insert_subprocess_mock(INIT_COMMAND + ('--storage-quota', '5G'))
31
+
32
+    module.initialize_repository(repository='repo', encryption_mode='repokey', storage_quota='5G')
33
+
34
+
35
+def test_initialize_repository_with_log_info_calls_borg_with_info_parameter():
36
+    insert_subprocess_mock(INIT_COMMAND + ('--info',))
37
+    insert_logging_mock(logging.INFO)
38
+
39
+    module.initialize_repository(repository='repo', encryption_mode='repokey')
40
+
41
+
42
+def test_initialize_repository_with_log_debug_calls_borg_with_debug_parameter():
43
+    insert_subprocess_mock(INIT_COMMAND + ('--debug',))
44
+    insert_logging_mock(logging.DEBUG)
45
+
46
+    module.initialize_repository(repository='repo', encryption_mode='repokey')
47
+
48
+
49
+def test_initialize_repository_with_local_path_calls_borg_via_local_path():
50
+    insert_subprocess_mock(('borg1',) + INIT_COMMAND[1:])
51
+
52
+    module.initialize_repository(repository='repo', encryption_mode='repokey', local_path='borg1')
53
+
54
+
55
+def test_initialize_repository_with_remote_path_calls_borg_with_remote_path_parameter():
56
+    insert_subprocess_mock(INIT_COMMAND + ('--remote-path', 'borg1'))
57
+
58
+    module.initialize_repository(repository='repo', encryption_mode='repokey', remote_path='borg1')

+ 4
- 2
tests/unit/test_verbosity.py View File

@@ -6,9 +6,11 @@ from borgmatic import verbosity as module
6 6
 
7 7
 
8 8
 def insert_logging_mock(log_level):
9
-    """ Mocks the isEnabledFor from python logging. """
9
+    '''
10
+    Mock the isEnabledFor from Python logging.
11
+    '''
10 12
     logging = flexmock(module.logging.Logger)
11
-    logging.should_receive('isEnabledFor').replace_with(lambda lvl: lvl >= log_level)
13
+    logging.should_receive('isEnabledFor').replace_with(lambda level: level >= log_level)
12 14
     logging.should_receive('getEffectiveLevel').replace_with(lambda: log_level)
13 15
 
14 16
 

Loading…
Cancel
Save