Compare commits

..

185 Commits

Author SHA1 Message Date
Christoph Loy
f67bb7f73c Bump version to 1.7.9 (#325) 2023-10-02 14:09:34 +02:00
Christoph Loy
530b1b646c Update restic to 0.16.0 (#324) 2023-10-02 11:08:28 +02:00
Major Hayden
3b57602fe8 Docs: Add Fedora installation choice (#320)
Fedora now has an autorestic package and users can install it directly
from Fedora's repositories.

Signed-off-by: Major Hayden <major@mhtx.net>
2023-08-01 13:10:12 +02:00
Mikel Olasagasti Uranga
045513234f Use Printf instead of Println (#318)
Testing in Fedora reports:

./root.go:58:4: (*github.com/fatih/color.Color).Println call has
possible Printf formatting directive %s
2023-07-31 10:03:18 +02:00
dependabot[bot]
78b0db50e0 Bump webpack from 5.75.0 to 5.76.1 in /docs/.codedoc (#295)
Bumps [webpack](https://github.com/webpack/webpack) from 5.75.0 to 5.76.1.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.75.0...v5.76.1)

---
updated-dependencies:
- dependency-name: webpack
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-22 15:25:32 +01:00
dependabot[bot]
62dd371d51 Bump webpack from 5.75.0 to 5.76.1 in /docs (#296)
Bumps [webpack](https://github.com/webpack/webpack) from 5.75.0 to 5.76.1.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/compare/v5.75.0...v5.76.1)

---
updated-dependencies:
- dependency-name: webpack
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-22 15:25:20 +01:00
dependabot[bot]
08766b75db Bump prismjs from 1.23.0 to 1.29.0 in /docs/.codedoc (#269)
Bumps [prismjs](https://github.com/PrismJS/prism) from 1.23.0 to 1.29.0.
- [Release notes](https://github.com/PrismJS/prism/releases)
- [Changelog](https://github.com/PrismJS/prism/blob/master/CHANGELOG.md)
- [Commits](https://github.com/PrismJS/prism/compare/v1.23.0...v1.29.0)

---
updated-dependencies:
- dependency-name: prismjs
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-22 15:25:05 +01:00
b3b7c8df95 update versions 2023-03-05 17:52:40 +01:00
2c5266c9a0 fix build system 2023-03-05 17:22:03 +01:00
087b293c39 version bump 2023-03-02 15:03:57 +01:00
Andreas
112a69d743 Allow env variables in path for rest backend (#280) 2023-03-02 15:00:39 +01:00
dependabot[bot]
37d55c691f Bump golang.org/x/text from 0.3.7 to 0.3.8 (#289)
Bumps [golang.org/x/text](https://github.com/golang/text) from 0.3.7 to 0.3.8.
- [Release notes](https://github.com/golang/text/releases)
- [Commits](https://github.com/golang/text/compare/v0.3.7...v0.3.8)

---
updated-dependencies:
- dependency-name: golang.org/x/text
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-01 20:22:18 +01:00
dependabot[bot]
715b6f791c Bump golang from 1.19-alpine to 1.20-alpine (#287)
Bumps golang from 1.19-alpine to 1.20-alpine.

---
updated-dependencies:
- dependency-name: golang
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-01 20:22:07 +01:00
dependabot[bot]
38b38c6805 Bump restic/restic from 0.15.0 to 0.15.1 (#286)
Bumps restic/restic from 0.15.0 to 0.15.1.

---
updated-dependencies:
- dependency-name: restic/restic
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-01 20:21:49 +01:00
c82db56069 Update config.go 2023-01-19 09:44:05 +01:00
27821dc3ef changelog 2023-01-18 22:45:46 +01:00
866975d32d update deps 2023-01-18 22:43:56 +01:00
dependabot[bot]
955ac0e323 Bump restic/restic from 0.14.0 to 0.15.0 (#283)
Bumps restic/restic from 0.14.0 to 0.15.0.

---
updated-dependencies:
- dependency-name: restic/restic
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-18 22:39:38 +01:00
Mariusz Kozakowski
1512db5b55 Fix regexp for AddedExtractor (#284)
Co-authored-by: mariom <11mariom@gmail.com>
2023-01-18 22:39:12 +01:00
dependabot[bot]
046331748c Bump golang from 1.18-alpine to 1.19-alpine (#268)
Bumps golang from 1.18-alpine to 1.19-alpine.

---
updated-dependencies:
- dependency-name: golang
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-09 17:31:54 +01:00
72a40eaaa2 update deps 2022-12-09 17:29:07 +01:00
ec61effe22 Merge branch 'master' of https://github.com/cupcakearmy/autorestic 2022-12-09 17:24:07 +01:00
d6acab94a5 use restic as base image 2022-12-09 17:24:03 +01:00
Daniel Brennand
d0d2fcf0f2 docs: add dbrennand.autorestic role (#250) 2022-10-29 21:32:48 +02:00
58fd41fafb consistent casing (#247) 2022-10-18 16:23:27 +02:00
Šimon Woidig
d0c4a32879 Update install.sh (#246)
Add check for bzip2 command existance
2022-10-18 16:17:35 +02:00
74979e9a2a Update config.go 2022-10-17 15:03:17 +02:00
Romain de Laage
3732dcf6ff Check for errors on forget after having backuped (#241) 2022-10-17 15:02:47 +02:00
Andreas Wagner
874ed52e3b add more architectures for linux build process (#243)
* add more architectures for linux build process

equivalent to 7d665fa1f4/helpers/build-release-binaries/main.go

* add solaris and s390x

Co-authored-by: Nicco <hi@nicco.io>
2022-10-06 15:41:01 +01:00
4d9a2b828e Update config.go 2022-09-13 15:16:21 +02:00
Chosto
b830667264 Use options when calling check or init (#199) 2022-09-13 15:15:02 +02:00
John Burkhardt
83eeb847ac Add command line flag to override docker image. (#233) 2022-09-13 15:04:12 +02:00
shahvirb
a89ba5a40a Update forget.md keep-within argument to be '14d' (#236)
keep-within duration can only have units 'y', 'm', 'd', and 'h'. The current documentation of '2w' yields:
```invalid argument "2w" for "--keep-within" flag: invalid unit 'w' found after number 2```

https://restic.readthedocs.io/en/latest/060_forget.html#removing-snapshots-according-to-a-policy
2022-09-13 14:59:46 +02:00
Romain de Laage
6990bf6adc Check for errors and forward on exec command (#227)
fix #226
2022-08-26 17:09:26 +02:00
Romain de Laage
2f407cf211 Add forget for copy backend (#223)
fix #221
2022-08-24 12:09:32 +02:00
489f3078fe bump version 2022-08-24 12:03:11 +02:00
Romain de Laage
e07dd0d991 Don't check if path is a directory (#220)
We only run a stat on the path to check there is no error
2022-08-22 17:28:38 +02:00
kenc
2b9dc9f17c Add test for locking behaviour (#211) 2022-06-27 09:03:34 +02:00
kenc
465bc037c2 Add lock tests (#209)
* Add lock tests

* Refactor setLock to accept key value pairs

This allows SetCron and Lock to use the same function setLockValue. It
also removes the need to call getLock explicitly in tests by returning
the lock object.
2022-06-06 12:59:47 +02:00
kenc
37a043afff Add location unit tests (#208) 2022-06-02 17:05:44 +02:00
kenc
e91b632181 Add backend unit tests (#207) 2022-06-01 14:58:38 +02:00
kenc
2b30998b9a Add options parsing unit tests (#205)
* Add options parsing unit tests

* Refactor into subtests
2022-05-30 12:56:25 +02:00
Varac
49b37a0a9a Add varacs ansible role (#201)
Co-authored-by: Nicco <hi@nicco.io>
2022-05-24 13:34:10 +02:00
3bc091f826 lean flag 2022-04-27 00:55:31 +02:00
5bcf5c9217 1.7.0 (#188)
* stream the output (#186)

* dont duplicate global flags (#187)

* docs for tagging

* fix self update path (#190)

* version bump & changelog
2022-04-27 00:48:52 +02:00
ff2e3714d1 1.6.2 2022-04-14 17:44:54 +02:00
2e6764223d bump go version in docker 2022-04-14 12:05:19 +02:00
434862ed4e 1.6.0 (#180)
* fix for #178

* restore options

* error codes

* update docs

* forget docs

* add option to auto forget

* add copy option

* update go version to enable generics

* copy docs

* changelog & version bump
2022-04-14 11:51:32 +02:00
2da440a1cd richtiattribution 2022-04-13 08:55:53 +02:00
0f6c34dc6b healthcheck example 2022-04-12 23:59:12 +02:00
3457fc01bf Merge pull request #176 from ninjabenji/ninjabenji-doc-update 2022-04-08 15:06:37 +02:00
ninjabenji
1e62f6cb05 DOC - Note on config filename extension
Seems the config file can be located wherever needed but not using an extension or using an extension other than .yml causes the following error
could not load config file
Unsupported Config Type ""
2022-04-08 10:31:42 +01:00
8112aceb30 Merge pull request #175 from ninjabenji/ninjabenji-backend-name-warning
Add lower case warning for backend name
2022-04-07 11:51:13 +02:00
ninjabenji
b4cc0f0ab1 Add warning about location names.
Location names need to be lower case in order to work properly.
2022-04-07 10:32:17 +01:00
ninjabenji
a3e539b0b7 Add line break in documentation 2022-04-07 10:00:28 +01:00
ninjabenji
79164e3de0 Add lower case warning for backend name
If uppercase characters are used in a backend name a check (and I assume other actions) will fail.
2022-04-06 21:28:25 +01:00
8b74a98836 better error handling 2022-03-18 13:06:19 +01:00
8a713e497d add ssh & update deps 2022-03-12 14:13:29 +01:00
71601ae7a6 Merge pull request #170 from fariszr/patch-2
Add OpenSSH to Autorestic Docker container
2022-03-12 14:11:03 +01:00
FarisZR
85d3f06b79 fix #169 2022-03-11 22:34:33 +03:00
5afad86e37 1.5.6 2022-03-10 16:36:26 +01:00
ba9e090695 Merge pull request #167 from fariszr/patch-1
Add bash to docker image
2022-03-10 16:32:43 +01:00
FarisZR
05def04770 Fix #166 2022-03-10 18:28:46 +03:00
d85470459f fix home directory 2022-02-16 21:58:09 +01:00
1f69a7974a bump go version 2022-02-16 21:52:12 +01:00
db9f5dea66 1.5.4 2022-02-16 21:42:54 +01:00
75160d4d6a Merge pull request #160 from cupcakearmy/1.5.3
1.5.3
2022-02-16 21:29:10 +01:00
92feaef5bb version bump 2022-02-16 21:28:13 +01:00
2a7e233cdb only check on config if it is getting used 2022-02-16 21:22:42 +01:00
a373c07fb0 contrib 2022-02-13 16:25:51 +01:00
ec9e2aebcd 1.5.2 2022-02-13 16:25:09 +01:00
7d87160706 Merge pull request #156 from jjromannet/verbose-config-loading
Add error handling to reading of config file.
2022-02-13 16:23:05 +01:00
8e1fe6af65 Merge pull request #157 from jjromannet/fix-copy-config-file-before-overriding
Make a copy of config before overriding it
2022-02-13 16:20:46 +01:00
Jan
65ba1f6ac1 Make a copy of config before overriding it 2022-02-09 20:35:29 +01:00
Jan
6bf4953003 Add error handling to reading config file. 2022-02-09 20:26:02 +01:00
27758a03fa add help message 2021-12-22 14:45:04 +01:00
bbdae05199 changelog 2021-12-06 17:27:59 +01:00
389490c4ea Merge pull request #136 from cupcakearmy/1.5.1
1.5.1
2021-12-06 17:24:19 +01:00
21b83b4c89 docker docs 2021-12-06 17:20:06 +01:00
982f9e0b5c better error handling 2021-12-06 12:13:18 +01:00
0c37af5588 #140 Docker version pinning 2021-12-06 11:59:58 +01:00
e3c378f2a1 release day 2021-11-24 09:57:10 +01:00
3b541665ae changelog 2021-11-24 09:45:23 +01:00
e0b2c99ccd contrib 2021-11-24 09:45:19 +01:00
2b14e6b1af remove print 2021-11-24 09:35:31 +01:00
1810af8d02 Merge pull request #137 from g-a-c/issues/130
refactor downloading of binaries
2021-11-23 13:01:09 +01:00
252968e15e ensure config is loaded before lock 2021-11-23 12:32:35 +01:00
26de4385ea version bump 2021-11-23 12:32:22 +01:00
0c71bea93e use own docker image 2021-11-21 21:10:32 +01:00
3029259d82 docs 2021-11-21 21:06:25 +01:00
Gavin Chappell
389f7c25dd refactor downloading of binaries
* If `/tmp` is a `tmpfs` or somehow not the same filesystem, `os.Rename()` will fail so use `io.Copy()` instead
* don't defer cleanup of `to` as this removes the newly-created file if the operation is successful, making `install` and `upgrade` _functionally_ `uninstall`
* defer cleanup of the temporary file since it will still be in place if `os.Rename()` fails

Fixes: #130
2021-11-20 19:30:46 +00:00
e055e28cfe Merge pull request #125 from cupcakearmy/1.5.0
1.5.0
2021-11-20 17:11:44 +01:00
ed3c17d678 migration docs 2021-11-20 17:09:42 +01:00
8802b74b47 changelog and docs 2021-11-20 17:03:54 +01:00
e94e7030fc docker image 2021-11-20 16:59:13 +01:00
c55e91b8ff version bump 2021-11-20 16:55:20 +01:00
170bdb81ad tags 2021-11-07 11:48:03 +01:00
4126436f7f migration docs 2021-11-07 11:42:45 +01:00
113a97c283 add config version to ensure compatibility 2021-11-06 22:00:44 +01:00
c250391f67 docs 2021-11-01 11:19:27 +01:00
d3b4915d25 deprecation notion 2021-11-01 09:09:29 +01:00
cd7a5cbc13 also enable azure and google cloud 2021-11-01 00:19:32 +01:00
3dd3956d64 support for rclone 2021-11-01 00:16:54 +01:00
59035da46a remove output 2021-10-31 23:58:08 +01:00
90914d2078 changelog 2021-10-31 23:35:52 +01:00
0ae374cd45 docs 2021-10-31 23:35:46 +01:00
3665cea62d foo 2021-10-31 23:11:57 +01:00
cf92d400c2 changelog 2021-10-31 23:11:06 +01:00
696bd14ac7 Merge pull request #124 from cupcakearmy/multiple-paths
Multiple paths
2021-10-31 23:09:23 +01:00
a68e3e426e simplify options handling 2021-10-31 23:07:12 +01:00
27e82c8529 Merge branch 'master' into multiple-paths 2021-10-31 22:54:14 +01:00
7f9251f06b Merge pull request #123 from cupcakearmy/1.4.1
allow all values from envs
2021-10-31 22:47:33 +01:00
09cfa4a98e changelog 2021-10-31 22:46:37 +01:00
05c3458a95 version bump 2021-10-31 22:45:54 +01:00
2826f9586d allow all values from envs 2021-10-31 22:45:03 +01:00
4fe241e6f3 support for multiple sources 2021-10-31 22:33:02 +01:00
d0b1b86fdd docker runner 2021-10-31 22:32:55 +01:00
14dd41d60d docs 2021-10-31 22:32:34 +01:00
bcfc734cd1 describe multiple sources 2021-10-31 22:32:30 +01:00
6817f494ef util to check if volume exists 2021-10-31 22:32:01 +01:00
2c46f0da0c restore arg help 2021-10-31 22:31:47 +01:00
ac756dfbde bug not showing error messages 2021-10-31 22:31:31 +01:00
f71425be5b Merge pull request #122 from n194/patch-1
Remove credit from AUR
2021-10-31 14:47:16 +01:00
n
d78fbb6650 Remove credit from AUR 2021-10-31 19:40:53 +09:00
7ad6f7ce9e Merge remote-tracking branch 'origin/master' into multiple-paths 2021-10-30 15:06:13 +02:00
de663f287c Merge pull request #118 from cupcakearmy/1.4.0
1.4.0
2021-10-30 15:02:27 +02:00
439076d7ab chengelog 2021-10-30 14:54:41 +02:00
8c30134f7c error handling for upgrade and uninstall 2021-10-30 14:50:27 +02:00
11d1da7468 use wget 2021-10-30 14:38:53 +02:00
92b1577634 use wget 2021-10-30 14:36:25 +02:00
a7944aed1f don't install restic as default 2021-10-30 14:36:16 +02:00
3b99e301e9 only use wget 2021-10-30 14:30:53 +02:00
246e6fc0d8 enable generic env variables 2021-10-30 13:48:44 +02:00
c2f9ed9204 multiple paths 2021-10-30 13:01:31 +02:00
4055ebf8e8 changelog 2021-10-29 18:35:24 +02:00
6be0a80b29 use built in function 2021-10-29 18:35:21 +02:00
efd4a7dfea version bump 2021-10-28 18:10:33 +02:00
440609220c support for global flags 2021-10-28 18:10:14 +02:00
fb6217d868 docs for global flags 2021-10-28 18:09:38 +02:00
1628384e1f docs 2021-10-28 17:35:51 +02:00
3e80e6d18e Merge pull request #117 from cupcakearmy/specific-location
specific location
2021-10-28 17:30:51 +02:00
83905d2993 specific location 2021-10-28 17:29:32 +02:00
ddc3accb30 Merge pull request #114 from cupcakearmy/1.3.0
1.3.0
2021-10-26 16:03:09 +02:00
7874512ec0 changelog 2021-10-26 16:02:30 +02:00
0b5f4017e4 version bump 2021-10-26 15:59:49 +02:00
88198c4fcb docs for hook envs 2021-10-26 15:57:47 +02:00
8cd759105f env for hooks 2021-10-26 15:57:40 +02:00
6137e31c3b allow argumentless flags 2021-10-25 18:29:59 +02:00
2789502c89 toc 2021-10-25 18:02:56 +02:00
b87381cd3b docs for envs 2021-10-25 18:02:51 +02:00
048a5ffed8 renamed env 2021-10-25 18:02:45 +02:00
02a8e461d4 ability to use keys from envs 2021-10-25 17:36:06 +02:00
a1abe13a39 docs on cron 2021-10-25 17:07:29 +02:00
ddf287f6f5 Merge pull request #113 from cupcakearmy/1.3.0
1.3.0
2021-10-22 17:59:25 +02:00
56f82ae656 pnpm & typo 2021-10-22 17:57:43 +02:00
95b1ca3297 Merge pull request #108 from FuzzyMistborn/patch-2
Add third ansible role to community docs
2021-10-07 12:14:08 +02:00
Fuzzy
7a8830cb2f Add third ansible role to community docs 2021-10-06 13:24:40 -04:00
959d19cbdb add XDG_CONFIG_HOME to config 2021-10-06 15:50:23 +02:00
9dc7763445 Merge pull request #103 from IronicBadger/master
fixes small typo
2021-09-01 10:31:53 +02:00
Alex Kretzschmar
50984f6771 typo 2021-08-31 18:35:11 -04:00
e80f200873 systemd units 2021-08-06 10:20:40 +02:00
86ae70672a right version 2021-08-05 21:58:48 +02:00
5c0788900f docs 2021-08-05 21:54:40 +02:00
20334a7e83 better config handling 2021-08-05 21:48:02 +02:00
7bebd04482 validate and show cwd 2021-07-12 19:10:36 +02:00
b9b8857bf4 aliases 2021-07-12 19:10:25 +02:00
dd6e618161 typo 2021-07-11 14:03:04 +02:00
a4b54f9f64 check config bugs 2021-07-11 13:51:04 +02:00
c2e88193cd Update contrib.md 2021-06-06 15:51:50 +02:00
a2ef69d96d Merge pull request #87 from somebox/patch-1
correction to B2 config in the example
2021-06-06 15:48:02 +02:00
Jeremy Seitz
d45949b028 correction to B2 config in the example
This example was missing the `env:` block in YAML, which if missing, caused `autorestic check` to compain:

``` * 'Backends[remote]' has invalid keys: b2_account_id, b2_account_key```
2021-06-06 14:27:26 +02:00
77d47cc697 add arm in install script 2021-05-25 22:57:42 +02:00
a3239c0f3b changelog 2021-05-17 22:40:27 +02:00
90cd3171e5 docs 2021-05-17 22:40:23 +02:00
61673bd88b allow options for backend 2021-05-17 22:34:14 +02:00
41736ea3c4 typos 2021-05-07 11:34:46 +02:00
a7779e04bd Merge pull request #75 from FuzzyMistborn/patch-2
Update update command in docs
2021-05-07 11:31:26 +02:00
Fuzzy
1326e7e53c Update update command in docs 2021-05-06 23:05:34 -04:00
478e193d78 forgot version bump 2021-05-06 16:17:45 +02:00
11d4c67dce docs 2021-05-06 16:14:29 +02:00
1643309957 changelog 2021-05-06 15:57:28 +02:00
e05386b0b5 add failure and success hooks 2021-05-06 15:55:32 +02:00
aebaf0a225 Merge branch 'master' of https://github.com/cupcakearmy/autorestic 2021-05-06 15:12:37 +02:00
c090013bf5 Update README.md 2021-05-06 15:12:11 +02:00
88c6949208 custom restic binary 2021-05-06 15:04:35 +02:00
69 changed files with 6472 additions and 9186 deletions

6
.dockerignore Normal file
View File

@@ -0,0 +1,6 @@
*
!**/*.go
!build
!cmd
!internal
!go.*

8
.github/dependabot.yml vendored Normal file
View File

@@ -0,0 +1,8 @@
# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
version: 2
updates:
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "weekly"

View File

@@ -3,26 +3,49 @@ name: Main
on:
push:
tags:
- 'v*.*.*'
- "v*.*.*"
jobs:
build:
docker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Docker Labels
id: meta
uses: crazy-max/ghaction-docker-meta@v4
with:
go-version: '^1.16.3'
images: cupcakearmy/autorestic
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
- name: Login to DockerHub
uses: docker/login-action@v2
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push
uses: docker/build-push-action@v3
with:
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-go@v3
with:
go-version: "^1.20"
- name: Build
run: go run build/build.go
- name: Sign
uses: tristan-weil/ghaction-checksum-sign-artifact@v1.0.1
with:
path: dist/*
- name: Release
uses: softprops/action-gh-release@v1
with:
files: dist/*
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@@ -5,11 +5,215 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [1.7.4] - 2023-01-18
### Fixed
- Transformer for extracting information. @11mariom
### Changed
- Bump docker restic version.
- Docs dependencies updated.
## [1.7.1] - 2022-04-27
### Fixed
- #178 Lean flag not working properly.
## [1.7.0] - 2022-04-27
### Changed
- #147 Stream output instead of buffering.
### Fixed
- #184 duplicate global options.
- #154 add docs for migration.
- #182 fix bug with upgrading custom restic with custom path.
## [1.6.2] - 2022-04-14
### Fixed
- Version bump in code.
## [1.6.1] - 2022-04-14
### Fixed
- Bump go version in docker file to 18.
## [1.6.0] - 2022-04-14
### Added
- support for copy command #145
- partial restore with `--include`, `--exclude`, `--iinclude`, `--iexclude` flags #161
- run forget automatically after backup #158
- exit codes to hooks as env variable #142
### Fixed
- Lean flag not removing all output #178
## [1.5.8] - 2022-03-18
### Fixed
- Better error handling for bad config files.
## [1.5.7] - 2022-03-11
### Added
- SSH in docker image. @fariszr
### Security
- Updated dependencies
## [1.5.6] - 2022-03-10
### Fixed
- Add bash in docker image for hooks. @fariszr
## [1.5.5] - 2022-02-16
### Changed
- Go version was updated from `1.16` to `1.17`
### Fixed
- Home directory was not being taken into account for loading configs.
## [1.5.4] - 2022-02-16
### Fixed
- Lean flag not omitting all output.
## [1.5.3] - 2022-02-16
### Fixed
- Error throwing not finding config even it's not being used.
## [1.5.2] - 2022-02-13
### Fixed
- Config loading @jjromannet
- Making a backup of the file @jjromannet
## [1.5.1] - 2021-12-06
### Changed
- use official docker image instead of installing rclone every time docker is used.
- docker docs
### Fixed
- lock file not always next to the config file.
- update / install bugs.
- lock docker image tag to the current autorestic version
- better error logging
## [1.5.0] - 2021-11-20
### Added
- Support for multiple paths.
- Improved error handling.
- Allow for specific snapshot to be restored.
- Docker image.
### Fixed
- rclone in docker volumes.
### Changed
- [Breaking Change] Declaration of docker volumes. See: https://autorestic.vercel.app/migration/1.4_1.5.
- [Breaking Change] Hooks default executing directory now defaults to the config file directory. See: https://autorestic.vercel.app/migration/1.4_1.5.
## [1.4.1] - 2021-10-31
### Fixed
- Numeric values from config files not being passed to env.
## [1.4.0] - 2021-10-30
### Added
- Allow specify to specify a backend for location backup.
- Global restic flags.
- Generic ENV support for backends.
### Changed
- Install now only requires `wget`.
- Env variable for the `KEY` has been renamed from `AUTORESTIC_[BACKEND NAME]_KEY` -> `AUTORESTIC_[BACKEND NAME]_RESTIC_PASSWORD`.
### Fixed
- Error handling during upgrade & uninstall.
## [1.3.0] - 2021-10-26
### Added
- Pass restic backup metadata as ENV to hooks.
- Support for `XDG_CONFIG_HOME` and `${HOME}/.config` as default locations for `.autorestic.yaml` file.
- Binary restic flags are now supported.
- Pass encryption keys from env variables or files.
## [1.2.0] - 2021-08-05
### Added
- Community page
- Support for yaml references and aliases.
### Fixed
- Better verbose output for hooks.
- Better error message for bad formatted configs.
## [1.1.2] - 2021-07-11
### Fixes
Don't check all backend when running `forget` or `exec` commands.
## [1.1.1] - 2021-05-17
### Added
- Options for backends.
## [1.1.0] - 2021-05-06
### Added
- use custom restic binary.
- success & failure hooks.
### Fixed
- don't skip other locations on failure.
## [1.0.9] - 2021-05-01
### Fixed
- Validation for docker volumes
- Validation for docker volumes.
## [1.0.8] - 2021-04-28
@@ -32,7 +236,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Support for rclone
- Support for rclone.
## [1.0.5] - 2021-04-24
@@ -45,17 +249,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Options to add rest username and password in config
- Options to add rest username and password in config.
### Fixed
- Don't add empty strings when saving config
- Don't add empty strings when saving config.
## [1.0.3] - 2021-04-20
### Fixed
- Auto upgrade script was not working on linux as linux does not support writing to the binary that is being executed
- Auto upgrade script was not working on linux as linux does not support writing to the binary that is being executed.
## [1.0.2] - 2021-04-20
@@ -71,7 +275,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Completion command for various shells
- Completion command for various shells.
## [1.0.0] - 2021-04-17

View File

@@ -19,11 +19,12 @@ Releases are automatically built by the github workflow and uploaded to the rele
1. Bump `VERSION` in `internal/config.go`.
2. Update `CHANGELOG.md`
3. Commit to master
4. Create a new release with the `v1.2.3` tag and mark as draft.
4. Create a new release with the `v1.2.3` tag and mark as pre-release.
5. The Github action will build the binaries, upload and mark the release as ready when done.
### Brew
1. Check the checksum with `shasum -a 256 autorestic-1.2.3.tar.gz`
2. Update `url` and `sha256` in the brew repo.
3. Submit PR
1. Download the latest release.
2. Check the checksum with `shasum -a 256 autorestic-1.2.3.tar.gz`
3. Update `url` and `sha256` in the brew repo.
4. Submit PR

13
Dockerfile Normal file
View File

@@ -0,0 +1,13 @@
FROM golang:1.20-alpine as builder
WORKDIR /app
COPY go.* .
RUN go mod download
COPY . .
RUN go build
FROM restic/restic:0.16.0
RUN apk add --no-cache rclone bash
COPY --from=builder /app/autorestic /usr/bin/autorestic
ENTRYPOINT []
CMD [ "autorestic" ]

View File

@@ -13,6 +13,9 @@
<br><br>
<a target="_blank" href="https://discord.gg/wS7RpYTYd2">
<img src="https://img.shields.io/discord/252403122348097536" alt="discord badge" />
<img src="https://img.shields.io/github/contributors/cupcakearmy/autorestic" alt="contributor badge" />
<img src="https://img.shields.io/github/downloads/cupcakearmy/autorestic/total" alt="downloads badge" />
<img src="https://img.shields.io/github/v/release/cupcakearmy/autorestic" alt="version badge" />
</a>
</p>
</p>
@@ -22,7 +25,7 @@
### 💭 Why / What?
Autorestic is a wrapper around the amazing [restic](https://restic.net/). While being amazing the restic cli can be a bit overwhelming and difficult to manage if you have many different location that you want to backup to multiple locations. This utility is aimed at making this easier 🙂
Autorestic is a wrapper around the amazing [restic](https://restic.net/). While being amazing the restic cli can be a bit overwhelming and difficult to manage if you have many different locations that you want to backup to multiple locations. This utility is aimed at making this easier 🙂.
### 🌈 Features

View File

@@ -4,7 +4,11 @@
package main
import (
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"fmt"
"io/ioutil"
"os"
"os/exec"
"path"
@@ -17,23 +21,51 @@ import (
var DIR, _ = filepath.Abs("./dist")
var targets = map[string][]string{
// "aix": {"ppc64"}, // Not supported by fsnotify
"darwin": {"amd64", "arm64"},
"freebsd": {"386", "amd64", "arm"},
"linux": {"386", "amd64", "arm", "arm64"},
"linux": {"386", "amd64", "arm", "arm64", "ppc64le", "mips", "mipsle", "mips64", "mips64le", "s390x"},
"netbsd": {"386", "amd64"},
"openbsd": {"386", "amd64"},
// "windows": {"386", "amd64"}, // Not supported by autorestic
"solaris": {"amd64"},
}
type buildOptions struct {
Target, Arch, Version string
}
func build(options buildOptions, wg *sync.WaitGroup) {
fmt.Printf("Building %s %s\n", options.Target, options.Arch)
const (
CHECKSUM_MD5 = "MD5SUMS"
CHECKSUM_SHA_1 = "SHA1SUMS"
CHECKSUM_SHA_256 = "SHA256SUMS"
)
type Checksums struct {
filename, md5, sha1, sha256 string
}
func writeChecksums(checksums *[]Checksums) {
FILE_MD5, _ := os.OpenFile(path.Join(DIR, CHECKSUM_MD5), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
defer FILE_MD5.Close()
FILE_SHA1, _ := os.OpenFile(path.Join(DIR, CHECKSUM_SHA_1), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
defer FILE_SHA1.Close()
FILE_SHA256, _ := os.OpenFile(path.Join(DIR, CHECKSUM_SHA_256), os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
defer FILE_SHA256.Close()
for _, checksum := range *checksums {
fmt.Fprintf(FILE_MD5, "%s %s\n", checksum.md5, checksum.filename)
fmt.Fprintf(FILE_SHA1, "%s %s\n", checksum.sha1, checksum.filename)
fmt.Fprintf(FILE_SHA256, "%s %s\n", checksum.sha256, checksum.filename)
}
}
func build(options buildOptions, wg *sync.WaitGroup, checksums *[]Checksums) {
defer wg.Done()
fmt.Printf("Building: %s %s\n", options.Target, options.Arch)
out := fmt.Sprintf("autorestic_%s_%s_%s", options.Version, options.Target, options.Arch)
out = path.Join(DIR, out)
out, _ = filepath.Abs(out)
fmt.Println(out)
// Build
{
@@ -62,22 +94,39 @@ func build(options buildOptions, wg *sync.WaitGroup) {
panic(err)
}
}
wg.Done()
// Checksum
{
file := out + ".bz2"
content, _ := ioutil.ReadFile(file)
*checksums = append(*checksums, Checksums{
filename: path.Base(file),
md5: fmt.Sprintf("%x", md5.Sum(content)),
sha1: fmt.Sprintf("%x", sha1.Sum(content)),
sha256: fmt.Sprintf("%x", sha256.Sum256(content)),
})
}
fmt.Printf("Built: %s\n", path.Base(out))
}
func main() {
os.RemoveAll(DIR)
v := internal.VERSION
checksums := []Checksums{}
// Build async
var wg sync.WaitGroup
for target, archs := range targets {
for _, arch := range archs {
wg.Add(1)
build(buildOptions{
go build(buildOptions{
Target: target,
Arch: arch,
Version: v,
}, &wg)
}, &wg, &checksums)
}
}
wg.Wait()
writeChecksums(&checksums)
}

View File

@@ -2,6 +2,7 @@ package cmd
import (
"fmt"
"strings"
"github.com/cupcakearmy/autorestic/internal"
"github.com/cupcakearmy/autorestic/internal/colors"
@@ -13,20 +14,24 @@ var backupCmd = &cobra.Command{
Use: "backup",
Short: "Create backups for given locations",
Run: func(cmd *cobra.Command, args []string) {
internal.GetConfig()
err := lock.Lock()
CheckErr(err)
defer lock.Unlock()
CheckErr(internal.CheckConfig())
selected, err := internal.GetAllOrSelected(cmd, false)
CheckErr(err)
errors := 0
for _, name := range selected {
location, _ := internal.GetLocation(name)
err := location.Backup(false)
if err != nil {
colors.Error.Println(err)
var splitted = strings.Split(name, "@")
var specificBackend = ""
if len(splitted) > 1 {
specificBackend = splitted[1]
}
location, _ := internal.GetLocation(splitted[0])
errs := location.Backup(false, specificBackend)
for _, err := range errs {
colors.Error.Printf("%s\n\n", err)
errors++
}
}

View File

@@ -11,6 +11,7 @@ var checkCmd = &cobra.Command{
Use: "check",
Short: "Check if everything is setup",
Run: func(cmd *cobra.Command, args []string) {
internal.GetConfig()
err := lock.Lock()
CheckErr(err)
defer lock.Unlock()

View File

@@ -2,6 +2,7 @@ package cmd
import (
"github.com/cupcakearmy/autorestic/internal"
"github.com/cupcakearmy/autorestic/internal/flags"
"github.com/cupcakearmy/autorestic/internal/lock"
"github.com/spf13/cobra"
)
@@ -11,7 +12,7 @@ var cronCmd = &cobra.Command{
Short: "Run cron job for automated backups",
Long: `Intended to be mainly triggered by an automated system like systemd or crontab. For each location checks if a cron backup is due and runs it.`,
Run: func(cmd *cobra.Command, args []string) {
internal.CRON_LEAN, _ = cmd.Flags().GetBool("lean")
internal.GetConfig()
err := lock.Lock()
CheckErr(err)
defer lock.Unlock()
@@ -23,5 +24,5 @@ var cronCmd = &cobra.Command{
func init() {
rootCmd.AddCommand(cronCmd)
cronCmd.Flags().Bool("lean", false, "only output information about actual backups")
cronCmd.Flags().BoolVar(&flags.CRON_LEAN, "lean", false, "only output information about actual backups")
}

View File

@@ -1,6 +1,8 @@
package cmd
import (
"fmt"
"github.com/cupcakearmy/autorestic/internal"
"github.com/cupcakearmy/autorestic/internal/colors"
"github.com/cupcakearmy/autorestic/internal/lock"
@@ -11,18 +13,30 @@ var execCmd = &cobra.Command{
Use: "exec",
Short: "Execute arbitrary native restic commands for given backends",
Run: func(cmd *cobra.Command, args []string) {
internal.GetConfig()
err := lock.Lock()
CheckErr(err)
defer lock.Unlock()
CheckErr(internal.CheckConfig())
selected, err := internal.GetAllOrSelected(cmd, true)
CheckErr(err)
var errors []error
for _, name := range selected {
colors.PrimaryPrint(" Executing on \"%s\" ", name)
backend, _ := internal.GetBackend(name)
backend.Exec(args)
err := backend.Exec(args)
if err != nil {
errors = append(errors, err)
}
}
if len(errors) > 0 {
for _, err := range errors {
colors.Error.Printf("%s\n\n", err)
}
CheckErr(fmt.Errorf("%d errors were found", len(errors)))
}
},
}

View File

@@ -10,12 +10,11 @@ var forgetCmd = &cobra.Command{
Use: "forget",
Short: "Forget and optionally prune snapshots according the specified policies",
Run: func(cmd *cobra.Command, args []string) {
internal.GetConfig()
err := lock.Lock()
CheckErr(err)
defer lock.Unlock()
CheckErr(internal.CheckConfig())
selected, err := internal.GetAllOrSelected(cmd, false)
CheckErr(err)
prune, _ := cmd.Flags().GetBool("prune")

View File

@@ -9,9 +9,11 @@ import (
)
var restoreCmd = &cobra.Command{
Use: "restore",
Use: "restore [snapshot id]",
Short: "Restore backup for location",
Args: cobra.MaximumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
internal.GetConfig()
err := lock.Lock()
CheckErr(err)
defer lock.Unlock()
@@ -24,7 +26,23 @@ var restoreCmd = &cobra.Command{
target, _ := cmd.Flags().GetString("to")
from, _ := cmd.Flags().GetString("from")
force, _ := cmd.Flags().GetBool("force")
err = l.Restore(target, from, force)
snapshot := ""
if len(args) > 0 {
snapshot = args[0]
}
// Get optional flags
optional := []string{}
for _, flag := range []string{"include", "exclude", "iinclude", "iexclude"} {
values, err := cmd.Flags().GetStringSlice(flag)
if err == nil {
for _, value := range values {
optional = append(optional, "--"+flag, value)
}
}
}
err = l.Restore(target, from, force, snapshot, optional)
CheckErr(err)
},
}
@@ -36,4 +54,10 @@ func init() {
restoreCmd.Flags().String("to", "", "Where to restore the data")
restoreCmd.Flags().StringP("location", "l", "", "Location to be restored")
restoreCmd.MarkFlagRequired("location")
// Passed on flags
restoreCmd.Flags().StringSliceP("include", "i", []string{}, "Include a pattern")
restoreCmd.Flags().StringSliceP("exclude", "e", []string{}, "Exclude a pattern")
restoreCmd.Flags().StringSlice("iinclude", []string{}, "Include a pattern, case insensitive")
restoreCmd.Flags().StringSlice("iexclude", []string{}, "Exclude a pattern, case insensitive")
}

View File

@@ -2,9 +2,12 @@ package cmd
import (
"os"
"path/filepath"
"strings"
"github.com/cupcakearmy/autorestic/internal"
"github.com/cupcakearmy/autorestic/internal/colors"
"github.com/cupcakearmy/autorestic/internal/flags"
"github.com/cupcakearmy/autorestic/internal/lock"
"github.com/spf13/cobra"
@@ -26,7 +29,7 @@ var rootCmd = &cobra.Command{
Version: internal.VERSION,
Use: "autorestic",
Short: "CLI Wrapper for restic",
Long: "Documentation: https://autorestic.vercel.app",
Long: "Documentation:\thttps://autorestic.vercel.app\nSupport:\thttps://discord.gg/wS7RpYTYd2",
}
func Execute() {
@@ -35,26 +38,53 @@ func Execute() {
func init() {
rootCmd.PersistentFlags().StringVarP(&cfgFile, "config", "c", "", "config file (default is $HOME/.autorestic.yml or ./.autorestic.yml)")
rootCmd.PersistentFlags().BoolVar(&internal.CI, "ci", false, "CI mode disabled interactive mode and colors and enables verbosity")
rootCmd.PersistentFlags().BoolVarP(&internal.VERBOSE, "verbose", "v", false, "verbose mode")
rootCmd.PersistentFlags().BoolVar(&flags.CI, "ci", false, "CI mode disabled interactive mode and colors and enables verbosity")
rootCmd.PersistentFlags().BoolVarP(&flags.VERBOSE, "verbose", "v", false, "verbose mode")
rootCmd.PersistentFlags().StringVar(&flags.RESTIC_BIN, "restic-bin", "restic", "specify custom restic binary")
rootCmd.PersistentFlags().StringVar(&flags.DOCKER_IMAGE, "docker-image", "cupcakearmy/autorestic:"+internal.VERSION, "specify a custom docker image")
cobra.OnInitialize(initConfig)
}
func initConfig() {
if ci, _ := rootCmd.Flags().GetBool("ci"); ci {
colors.DisableColors(true)
internal.VERBOSE = true
flags.VERBOSE = true
}
if cfgFile != "" {
viper.SetConfigFile(cfgFile)
viper.AutomaticEnv()
if viper.ConfigFileUsed() == "" {
colors.Error.Printf("cannot read config file %s\n", cfgFile)
os.Exit(1)
}
} else {
home, err := homedir.Dir()
CheckErr(err)
configPaths := []string{"."}
viper.AddConfigPath(".")
viper.AddConfigPath(home)
viper.SetConfigName(".autorestic")
// Home
if home, err := homedir.Dir(); err == nil {
configPaths = append(configPaths, home)
}
// XDG_CONFIG_HOME
{
prefix, found := os.LookupEnv("XDG_CONFIG_HOME")
if !found {
if home, err := homedir.Dir(); err != nil {
prefix = filepath.Join(home, ".config")
}
}
xdgConfig := filepath.Join(prefix, "autorestic")
configPaths = append(configPaths, xdgConfig)
}
for _, cfgPath := range configPaths {
viper.AddConfigPath(cfgPath)
}
if flags.VERBOSE {
colors.Faint.Printf("Using config paths: %s\n", strings.Join(configPaths, " "))
}
cfgFileName := ".autorestic"
viper.SetConfigName(cfgFileName)
viper.AutomaticEnv()
}
viper.AutomaticEnv()
}

View File

@@ -9,12 +9,12 @@ var uninstallCmd = &cobra.Command{
Use: "uninstall",
Short: "Uninstall restic and autorestic",
Run: func(cmd *cobra.Command, args []string) {
noRestic, _ := cmd.Flags().GetBool("no-restic")
bins.Uninstall(!noRestic)
restic, _ := cmd.Flags().GetBool("restic")
bins.Uninstall(restic)
},
}
func init() {
rootCmd.AddCommand(uninstallCmd)
uninstallCmd.Flags().Bool("no-restic", false, "do not uninstall restic.")
uninstallCmd.Flags().Bool("restic", false, "also uninstall restic.")
}

View File

@@ -9,13 +9,13 @@ var upgradeCmd = &cobra.Command{
Use: "upgrade",
Short: "Upgrade autorestic and restic",
Run: func(cmd *cobra.Command, args []string) {
noRestic, _ := cmd.Flags().GetBool("no-restic")
err := bins.Upgrade(!noRestic)
restic, _ := cmd.Flags().GetBool("restic")
err := bins.Upgrade(restic)
CheckErr(err)
},
}
func init() {
rootCmd.AddCommand(upgradeCmd)
upgradeCmd.Flags().Bool("no-restic", false, "also update restic")
upgradeCmd.Flags().Bool("restic", true, "also update restic")
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,5 @@
{
"dependencies": {
"@codedoc/core": "^0.2.23"
"@codedoc/core": "^0.3.2"
}
}

View File

@@ -14,6 +14,7 @@
> > [Overview](/location/options)
> > [Excluding Files](/location/exclude)
> > [Forget Policy](/location/forget)
> > [Copy](/location/copy)
>
> [Cron](/location/cron)
> [Docker Volumes](/location/docker)
@@ -22,6 +23,8 @@
>
> [Overview](/backend/overview)
> [Available Backends](/backend/available)
> [Options](/backend/options)
> [Environment](/backend/env)
> :Collapse label=CLI
>
@@ -36,10 +39,16 @@
> [Exec](/cli/exec)
> [Install](/cli/install)
> [Uninstall](/cli/uninstall)
> [Update](/cli/update)
> [Upgrade](/cli/upgrade)
> :Collapse label=Migration
>
> [0.x → 1.0](/migration/0.x_1.0)
> [1.4 → 1.5](/migration/1.4_1.5)
[Examples](/examples)
[Docker](/docker)
[QA](/qa)
[Community](/community)
[Contributors](/contrib)

View File

@@ -4,6 +4,8 @@ In theory [all the restic backends](https://restic.readthedocs.io/en/stable/030_
Those tested are the following:
> You can also [specify the `env` variables in a config file](/backend/env) to separate them from the config file.
## Local
```yaml
@@ -19,9 +21,9 @@ backends:
backends:
name-of-backend:
type: b2
path: 'backblaze_bucketID'
path: 'bucket_name'
# Or With a path
# path: 'backblaze_bucketID:/some/path'
# path: 'bucket_name:/some/path'
env:
B2_ACCOUNT_ID: 'backblaze_keyID'
B2_ACCOUNT_KEY: 'backblaze_applicationKey'
@@ -29,7 +31,7 @@ backends:
#### API Keys gotcha
If you use a _File name prefix_ when making the application key, do not include a leading slash. Make sure to include this prefix in the path (e.g. `path: 'backblaze_bucketID:my/path'`).
If you use a _File name prefix_ when making the application key, do not include a leading slash. Make sure to include this prefix in the path (e.g. `path: 'bucket_name:my/path'`).
## S3 / Minio

View File

@@ -0,0 +1,67 @@
# Environment
> ⚠ Available since version `v1.4.0`
Sometimes it's favorable not having the encryption keys in the config files.
For that `autorestic` allows passing the env variables to backend password as `ENV` variables, or through an env file.
You can also pass whatever `env` variable to restic by prefixing it with `AUTORESTIC_[BACKEND NAME]_`.
> Env variables and file overwrite the config file in the following order:
>
> Env Variables > Env File (`.autorestic.env`) > Config file (`.autorestic.yaml`)
## Env file
Alternatively `autorestic` can load an env file, located next to `.autorestic.yml` called `.autorestic.env`.
```
AUTORESTIC_FOO_RESTIC_PASSWORD=secret123
```
### Example with repository password
The syntax for the `ENV` variables is as follows: `AUTORESTIC_[BACKEND NAME]_RESTIC_PASSWORD`.
```yaml | autorestic.yaml
backend:
foo:
type: ...
path: ...
key: secret123 # => AUTORESTIC_FOO_RESTIC_PASSWORD=secret123
```
This means we could remove `key: secret123` from `.autorestic.yaml` and execute as follows:
```bash
AUTORESTIC_FOO_RESTIC_PASSWORD=secret123 autorestic backup ...
```
### Example with Backblaze B2
```yaml | autorestic.yaml
backends:
bb:
type: b2
path: myBucket
key: myPassword
env:
B2_ACCOUNT_ID: 123
B2_ACCOUNT_KEY: 456
```
You could create an `.autorestic.env` or pass the following `ENV` variables to autorestic:
```
AUTORESTIC_BB_RESTIC_PASSWORD=myPassword
AUTORESTIC_BB_B2_ACCOUNT_ID=123
AUTORESTIC_BB_B2_ACCOUNT_KEY=456
```
```yaml | autorestic.yaml
backends:
bb:
type: b2
path: myBucket
```
> :ToCPrevNext

View File

@@ -0,0 +1,19 @@
# Options
> For more detail see the [location docs](/location/options) for options, as they are the same.
```yaml
backend:
foo:
type: ...
path: ...
options:
backup:
tag:
- foo
- bar
```
In this example, whenever `autorestic` runs `restic backup` it will append a `--tag abc --tag` to the native command.
> :ToCPrevNext

View File

@@ -2,7 +2,11 @@
Backends are the outputs of the backup process. Each location needs at least one.
Note: names of backends MUST be lower case!
```yaml | .autorestic.yml
version: 2
backends:
name-of-backend:
type: local

View File

@@ -14,4 +14,12 @@ autorestic backup -a
autorestic backup -l foo -l bar
```
## Specific location
`autorestic` also allows selecting specific backends for a location with the `location@backend` syntax.
```bash
autorestic backup -l location@backend
```
> :ToCPrevNext

View File

@@ -4,7 +4,7 @@
autorestic forget [-l, --location] [-a, --all] [--dry-run] [--prune]
```
This will prune and remove old data form the backends according to the [keep policy you have specified for the location](/location/forget)
This will prune and remove old data form the backends according to the [keep policy you have specified for the location](/location/forget).
The `--dry-run` flag will do a dry run showing what would have been deleted, but won't touch the actual data.

View File

@@ -2,7 +2,7 @@
## `-c, --config`
Specify the config file to be used.
Specify the config file to be used (must use .yml as an extension).
If omitted `autorestic` will search for for a `.autorestic.yml` in the current directory and your home directory.
```bash
@@ -27,4 +27,12 @@ Verbose mode will show the output of the native restic commands that are otherwi
autorestic --verbose backup -a
```
## `--restic-bin`
With `--restic-bin` you can specify to run a specific restic binary. This can be useful if you want to [create a custom binary with root access that can be executed by any user](https://restic.readthedocs.io/en/stable/080_examples.html#full-backup-without-root).
```bash
autorestic --restic-bin /some/path/to/my/custom/restic/binary
```
> :ToCPrevNext

View File

@@ -1,12 +1,12 @@
# Restore
```bash
autorestic restore [-l, --location] [--from backend] [--to <out dir>] [-f, --force]
autorestic restore [-l, --location] [--from backend] [--to <out dir>] [-f, --force] [snapshot]
```
This will restore all the locations to the selected target. If for one location there are more than one backends specified autorestic will take the first one.
This will restore the location to the selected target. If for one location there are more than one backends specified autorestic will take the first one. If no specific snapshot is specified `autorestic` will use `latest`.
The `--to` path has to be empty as no data will be overwritten by default. If you are sure you can pass the `-f, --force` flag and the data will be overwritten in the destination. However note that this will overwrite all the data existent in the backup, not only the 1 file that is missing e.g.
If you are sure you can pass the `-f, --force` flag and the data will be overwritten in the destination. However note that this will overwrite all the data existent in the backup, not only the 1 file that is missing e.g.
## Example

View File

@@ -1,11 +0,0 @@
# Update
Autorestic can update itself! Super handy right? Simply run autorestic update and we will check for you if there are updates for restic and autorestic and install them if necessary.
```bash
autorestic update
```
Updates both restic and autorestic automagically.
> :ToCPrevNext

View File

@@ -0,0 +1,11 @@
# Upgrade
Autorestic can upgrade itself! Super handy right? Simply run autorestic upgrade and we will check for you if there are updates for restic and autorestic and install them if necessary.
```bash
autorestic upgrade
```
Updates both restic and autorestic automagically.
> :ToCPrevNext

View File

@@ -0,0 +1,13 @@
# 🏘 Community
A list of community driven projects. (No official affiliation)
- SystemD Units: <https://gitlab.com/py_crash/autorestic-systemd-units>
- Docker image: <https://github.com/pascaliske/docker-autorestic>
- Ansible Role: <https://github.com/adsanz/ansible-restic-role>
- Ansible Role: <https://github.com/ItsNotGoodName/ansible-role-autorestic>
- Ansible Role: <https://github.com/FuzzyMistborn/ansible-role-autorestic>
- Ansible Role: <https://0xacab.org/varac-projects/ansible-role-autorestic>
- Ansible Role: <https://github.com/dbrennand/ansible-role-autorestic>
> :ToCPrevNext

View File

@@ -16,6 +16,8 @@ You can also specify a custom file with the `-c path/to/some/config.yml`
## Example configuration
```yaml | .autorestic.yml
version: 2
locations:
home:
from: /home/me
@@ -31,12 +33,55 @@ backends:
remote:
type: b2
path: 'myBucket:backup/home'
B2_ACCOUNT_ID: account_id
B2_ACCOUNT_KEY: account_key
env:
B2_ACCOUNT_ID: account_id
B2_ACCOUNT_KEY: account_key
hdd:
type: local
path: /mnt/my_external_storage
```
## Aliases
A handy tool for more advanced configurations is to use yaml aliases.
These must be specified under the global `extras` key in the `.autorestic.yml` config file.
Aliases allow to reuse snippets of config throughout the same file.
The following example shows how the locations `a` and `b` share the same hooks and forget policies.
```yaml | .autorestic.yml
version: 2
extras:
hooks: &foo
before:
- echo "Hello"
after:
- echo "kthxbye"
policies: &bar
keep-daily: 14
keep-weekly: 52
backends:
# ...
locations:
a:
from: /data/a
to: some
hooks:
<<: *foo
options:
forget:
<<: *bar
b:
from: data/b
to: some
hooks:
<<: *foo
options:
forget:
<<: *bar
```
> :ToCPrevNext

View File

@@ -2,15 +2,20 @@
This amazing people helped the project!
- @agateblue - Docs, Pruning, S3
- @david-boles - Docs
- @SebDanielsson - Brew
- @n194 - AUR Package
- @jin-park-dev - Typos
- @sumnerboy12 - Typos
- @FuzzyMistborn - Typos
- @ChanceM - Typos
- @TheForcer - Typos
- @themorlan - Typos
- @agateblue - Docs, Pruning, S3.
- @g-a-c - Update/Install bugs.
- @jjromannet - Bug fixes.
- @fariszr - Docker image improvements.
- @david-boles - Docs.
- @SebDanielsson - Brew.
- @n194 - AUR Package.
- @olofvndrhr - Healthchecks example.
- @jin-park-dev - Typos.
- @sumnerboy12 - Typos.
- @FuzzyMistborn - Typos.
- @ChanceM - Typos.
- @TheForcer - Typos.
- @themorlan - Typos.
- @somebox - Typos.
> :ToCPrevNext

28
docs/markdown/docker.md Normal file
View File

@@ -0,0 +1,28 @@
# 🐳 Docker
The docker image is build with rclone and restic already included. It's ment more as a utility image.
## Remote hosts
For remote backups (S3, B2, GCS, etc.) it's quite easy, as you only need to mount the config file and the data to backup.
```bash
docker run --rm \\
-v $(pwd):/data \\
cupcakearmy/autorestic \\
autorestic backup -va -c /data/.autorestic.yaml
```
## Rclone
For rclone you will have to also mount the rclone config file to `/root/.config/rclone/rclone.conf`.
To check where it is located you can run the following command: `rclone config file`.
**Example**
```bash
docker run \\
-v /home/user/.config/rclone/rclone.conf:/root/.config/rclone/rclone.conf:ro \\
...
```

View File

@@ -14,4 +14,27 @@ This can come in handy if a backup process crashed or if it was accidentally can
autorestic exec -b my-backend -- unlock
```
## Use hooks to integrate with [healthchecks](https://healthchecks.io/)
> Thanks to @olofvndrhr for providing it ❤️
```yaml
extras:
healthchecks: &healthchecks
hooks:
before:
- 'curl -m 10 --retry 5 -X POST -H "Content-Type: text/plain" --data "Starting backup for location: ${AUTORESTIC_LOCATION}" https://<healthchecks-url>/ping/<uid>/start'
failure:
- 'curl -m 10 --retry 5 -X POST -H "Content-Type: text/plain" --data "Backup failed for location: ${AUTORESTIC_LOCATION}" https://<healthchecks-url>/ping/<uid>/fail'
success:
- 'curl -m 10 --retry 5 -X POST -H "Content-Type: text/plain" --data "Backup successful for location: ${AUTORESTIC_LOCATION}" https://<healthchecks-url>/ping/<uid>'
locations:
something:
<<: *healthchecks
from: /somewhere
to:
- somewhere-else
```
> :ToCPrevNext

View File

@@ -2,14 +2,20 @@
Linux & macOS. Windows is not supported. If you have problems installing please open an issue :)
Autorestic requires `curl`, `wget` and `bzip2` to be installed. For most systems these should be already installed.
Autorestic requires `bash`, `wget` and `bzip2` to be installed. For most systems these should be already installed.
```bash
curl -s https://raw.githubusercontent.com/CupCakeArmy/autorestic/master/install.sh | bash
wget -qO - https://raw.githubusercontent.com/cupcakearmy/autorestic/master/install.sh | bash
```
## Alternatives
### Docker
There is an official docker image over at [cupcakearmy/autorestic](https://hub.docker.com/r/cupcakearmy/autorestic).
For some examples see [here](/docker).
### Manual
You can download the right binary from the release page and simply copy it to `/usr/local/bin` or whatever path you prefer. Autoupdates will still work.
@@ -18,8 +24,12 @@ You can download the right binary from the release page and simply copy it to `/
If you are on macOS you can install through brew: `brew install autorestic`.
### Fedora
Fedora users can install the [autorestic](https://src.fedoraproject.org/rpms/autorestic/) package with `dnf install autorestic`.
### AUR
If you are on Arch there is an [AUR Package](https://aur.archlinux.org/packages/autorestic-bin/) by @n194.
~~If you are on Arch there is an [AUR Package](https://aur.archlinux.org/packages/autorestic-bin/) (looking for maintainers).~~ - Deprecated
> :ToCPrevNext

View File

@@ -0,0 +1,31 @@
# Copy
Instead of specifying multiple `to` backends for a given `location` you can also use the `copy` option. Instead of recalculating the backup multiple times, you can copy the freshly copied snapshot from one backend to the other, avoiding recomputation.
###### Example
```yaml | .autorestic.yml
locations:
my-location:
from: /data
to:
- a #Fast
- b #Fast
- c #Slow
```
Becomes
```yaml | .autorestic.yml
locations:
my-location:
from: /data
to:
- a
- b
copy:
a:
- c
```
Instead of backing up to each backend separately, you can choose that the snapshot created to `a` will be copied over to `c`, avoiding heavy computation on `c`.

View File

@@ -37,11 +37,17 @@ Then paste this at the bottom of the file and save it. Note that in this specifi
PATH="/usr/local/bin:/usr/bin:/bin"
# Example running every 5 minutes
*/5 * * * * autorestic --ci cron
*/5 * * * * autorestic -c /path/to/my/.autorestic.yml --ci cron
```
> The `--ci` option is not required, but recommended
To debug a cron job you can use
```bash
*/5 * * * * autorestic -c /path/to/my/.autorestic.yml --ci cron > /tmp/autorestic.log 2>&1
```
Now you can add as many `cron` attributes as you wish in the config file ⏱
> Also note that manually triggered backups with `autorestic backup` will not influence the cron timeline, they are willingly not linked.

View File

@@ -3,7 +3,7 @@
autorestic supports docker volumes directly, without needing them to be mounted to the host filesystem.
```yaml | docker-compose.yml
version: '3.7'
version: '3.8'
volumes:
data:
@@ -18,13 +18,9 @@ services:
```yaml | .autorestic.yml
locations:
- name: hello
from: volume:my-data
to:
- remote
backends:
- name: remote
foo:
from: my-data
type: volume
# ...
```

View File

@@ -7,6 +7,8 @@ This is based on [Restic's snapshots policies](https://restic.readthedocs.io/en/
> **Note** This is a full example, of course you also can specify only one of them
```yaml | .autorestic.yml
version: 2
locations:
etc:
from: /etc
@@ -19,7 +21,37 @@ locations:
keep-weekly: 1 # keep 1 last weekly snapshots
keep-monthly: 12 # keep 12 last monthly snapshots
keep-yearly: 7 # keep 7 last yearly snapshots
keep-within: '2w' # keep snapshots from the last 2 weeks
keep-within: '14d' # keep snapshots from the last 14 days
```
## Globally
You can specify global forget policies that would be applied to all locations:
```yaml | .autorestic.yml
version: 2
global:
forget:
keep-daily: 30
keep-weekly: 52
```
## Automatically forget after backup
You can also configure `autorestic` to automatically run the forget command for you after every backup. You can do that by specifying the `forget` option.
```yaml | .autorestic.yml
version: 2
locations:
etc:
from: /etc
to: local
forget: prune # Or only "yes" if you don't want to prune
options:
forget:
keep-last: 5
```
> :ToCPrevNext

View File

@@ -2,7 +2,14 @@
If you want to perform some commands before and/or after a backup, you can use hooks.
They consist of a list of `before`/`after` commands that will be executed in the same directory as the target `from`.
They consist of a list of commands that will be executed in the same directory as the target `from`.
The following hooks groups are supported, none are required:
- `before`
- `after`
- `failure`
- `success`
```yml | .autorestic.yml
locations:
@@ -11,10 +18,63 @@ locations:
to: my-backend
hooks:
before:
- echo "Hello"
- echo "Human"
- echo "One"
- echo "Two"
- echo "Three"
after:
- echo "kthxbye"
- echo "Byte"
failure:
- echo "Something went wrong"
success:
- echo "Well done!"
```
## Flowchart
1. `before` hook
2. Run backup
3. `after` hook
4. - `success` hook if no errors were found
- `failure` hook if at least one error was encountered
If the `before` hook encounters errors the backup and `after` hooks will be skipped and only the `failed` hooks will run.
## Environment variables
All hooks are exposed to the `AUTORESTIC_LOCATION` environment variable, which contains the location name.
The `after` and `success` hooks have access to additional information with the following syntax:
```bash
AUTORESTIC_[TYPE]_[I]
AUTORESTIC_[TYPE]_[BACKEND_NAME]
```
Every type of metadata is appended with both the name of the backend associated with and the number in which the backends where executed.
### Available Metadata Types
- `SNAPSHOT_ID`
- `PARENT_SNAPSHOT_ID`
- `FILES_ADDED`
- `FILES_CHANGED`
- `FILES_UNMODIFIED`
- `DIRS_ADDED`
- `DIRS_CHANGED`
- `DIRS_UNMODIFIED`
- `ADDED_SIZE`
- `PROCESSED_FILES`
- `PROCESSED_SIZE`
- `PROCESSED_DURATION`
#### Example
Assuming you have a location `bar` that backs up to a single backend named `foo` you could expect the following env variables:
```bash
AUTORESTIC_LOCATION=bar
AUTORESTIC_FILES_ADDED_0=42
AUTORESTIC_FILES_ADDED_FOO=42
```
> :ToCPrevNext

View File

@@ -1,6 +1,32 @@
# Options
For the `backup` and `forget` commands you can pass any native flags to `restic`.
For the `backup` and `forget` commands you can pass any native flags to `restic`. In addition you can specify flags for every command with `all`.
If flags don't start with `-` they will get prefixed with `--`.
Flags without arguments can be set to `true`. They will be handled accordingly.
> It is also possible to set options for an [entire backend](/backend/options) or globally (see below).
```yaml
locations:
foo:
# ...
options:
all:
some-flag: 123
# Equivalent to
--some-flag: 123
backup:
boolean-flag: true
tag:
- foo
- bar
```
## Example
In this example, whenever `autorestic` runs `restic backup` it will append a `--tag foo --tag bar` to the native command.
```yaml
locations:
@@ -14,6 +40,28 @@ locations:
- bar
```
In this example, whenever `autorestic` runs `restic backup` it will append a `--tag abc --tag` to the native command.
## Priority
Options can be set globally, on the backends or on the locations.
The priority is as follows: `location > backend > global`.
## Global Options
It is possible to specify global flags that will be run every time restic is invoked. To do so specify them under `global` in your config file.
```yaml
global:
all:
cache-dir: ~/restic
backup:
tag:
- foo
backends:
# ...
locations:
# ...
```
> :ToCPrevNext

View File

@@ -3,10 +3,17 @@
Locations can be seen as the input to the backup process. Generally this is simply a folder.
The paths can be relative from the config file. A location can have multiple backends, so that the data is secured across multiple servers.
Note: names of locations MUST be lower case!
```yaml | .autorestic.yml
version: 2
locations:
my-location-name:
from: path/to/backup
# Or multiple
# from:
# - /a
# - /b
to:
- name-of-backend
- also-backup-to-this-backend
@@ -14,7 +21,7 @@ locations:
## `from`
This is the source of the location.
This is the source of the location. Can be an `array` for multiple sources.
#### How are paths resolved?

View File

@@ -1,6 +1,4 @@
# Update
## From `0.x` to `1.0`
# From `0.x` to `1.0`
Most of the config file is remained compatible, however to clean up the backends custom environment variables were moved from the root object to an `env` object.

View File

@@ -0,0 +1,68 @@
# Migration from `1.4` to `1.5`
## ⚠️ Important notes
The way snapshots are referenced in the `restore` and `prune` commands has been changed. Before they were referenced by the path. Now every backup is tagged and those tags are then referenced in the cli. This means that when running restore and forget commands old backups are not taken into account anymore.
## Config files
- The config file now required to have a version number. This has to be added with `version: 2` at the root.
- Hooks now optionally support `dir: /some/dir` in the [options object](https://pkg.go.dev/github.com/cupcakearmy/autorestic/internal#Hooks).
- Docker volumes don't get prefixed with `volume:` anymore, rather you have to set the `type: volume` in the [location config](https://pkg.go.dev/github.com/cupcakearmy/autorestic/internal#Hooks).
See detailed instructions below.
## Config Version
```yaml
version: 2 # Added
backends:
# ...
```
## Hooks
Since `1.5` multiple sources for a location are possible.
For this reason, while before hooks where executed in the folder of the source, now they are executed in the directory of the config `.autorestic.yaml`.
You can overwrite this behavior with the new `dir` option in the hook section of the config.
```yaml
locations:
l1:
# ...
from: /foo/bar
hooks:
dir: /foo/bar
before: pwd
```
## Docker volumes
The syntax with docker volumes has changed and needs to be adjusted.
```yaml
# Before
locations:
foo:
from: volume:my-data
```
```yaml
# After
locations:
foo:
from: my-data
type: volume
```
## Tagging
Autorestic changed the way backups are referenced. Before we took the paths as the identifying information. Now autorestic uses native restic tags to reference them. This means that old backups are not referenced. You can the old snapshots manually. An example can be shown below.
```bash
autorestic exec -va -- tag --add ar:location:LOCATION_NAME # Only if you have only one location
```
> :ToCPrevNext

View File

@@ -0,0 +1,4 @@
# Migration
- [From 0.x to 1.0](/migration/0.x_1.0)
- [From 1.4 to 1.5](/migration/1.4_1.5)

View File

@@ -3,7 +3,7 @@
## Installation
```bash
curl -s https://raw.githubusercontent.com/CupCakeArmy/autorestic/master/install.sh | bash
wget -qO - https://raw.githubusercontent.com/cupcakearmy/autorestic/master/install.sh | bash
```
See [installation](/installation) for alternative options.
@@ -25,9 +25,15 @@ For a quick overview:
> Note that the data is automatically encrypted on the server. The key will be generated and added to your config file. Every backend will have a separate key. **You should keep a copy of the keys or config file somewhere in case your server dies**. Otherwise DATA IS LOST!
```yaml | .autorestic.yml
version: 2
locations:
home:
from: /home/me
from: /home
# Or multiple
# from:
# - /foo
# - /bar
to: remote
important:

2611
docs/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,6 +5,6 @@
"dev": "codedoc serve"
},
"dependencies": {
"@codedoc/cli": "^0.2.8"
"@codedoc/cli": "^0.3.0"
}
}

31
go.mod
View File

@@ -1,13 +1,36 @@
module github.com/cupcakearmy/autorestic
go 1.16
go 1.20
require (
github.com/blang/semver/v4 v4.0.0
github.com/buger/goterm v1.0.0
github.com/fatih/color v1.10.0
github.com/fatih/color v1.13.0
github.com/joho/godotenv v1.4.0
github.com/mitchellh/go-homedir v1.1.0
github.com/robfig/cron v1.2.0
github.com/spf13/cobra v1.1.3
github.com/spf13/viper v1.7.1
github.com/spf13/cobra v1.4.0
github.com/spf13/viper v1.11.0
)
require (
github.com/fsnotify/fsnotify v1.5.1 // indirect
github.com/hashicorp/hcl v1.0.0 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/magiconair/properties v1.8.6 // indirect
github.com/mattn/go-colorable v0.1.12 // indirect
github.com/mattn/go-isatty v0.0.14 // indirect
github.com/mitchellh/mapstructure v1.4.3 // indirect
github.com/pelletier/go-toml v1.9.4 // indirect
github.com/pelletier/go-toml/v2 v2.0.0-beta.8 // indirect
github.com/spf13/afero v1.8.2 // indirect
github.com/spf13/cast v1.4.1 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/subosito/gotenv v1.2.0 // indirect
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
golang.org/x/text v0.3.8 // indirect
gopkg.in/ini.v1 v1.66.4 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
)

487
go.sum
View File

@@ -3,214 +3,216 @@ cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMT
cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To=
cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4=
cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M=
cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc=
cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk=
cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs=
cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc=
cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY=
cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI=
cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk=
cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY=
cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg=
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw=
cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA=
cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU=
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
github.com/buger/goterm v1.0.0 h1:ZB6uUlY8+sjJyFGzz2WpRqX2XYPeXVgtZAOJMwOsTWM=
github.com/buger/goterm v1.0.0/go.mod h1:16STi3LquiscTIHA8SXUNKEa/Cnu4ZHBH8NsCaWgso0=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg=
github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM=
github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fsnotify/fsnotify v1.5.1 h1:mZcQUHVQUQWoPXXtuf9yuEXKudkV2sx1E06UadKWpgI=
github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc=
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8=
github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY=
github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo=
github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/mitchellh/mapstructure v1.4.3 h1:OVowDSCllw/YjdLkam3/sm7wEtOy59d8ndGgCcyj8cs=
github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/pelletier/go-toml v1.9.4 h1:tjENF6MfZAg8e4ZmZTeWaWiT2vXtsoO6+iuOjFhECwM=
github.com/pelletier/go-toml v1.9.4/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pelletier/go-toml/v2 v2.0.0-beta.8 h1:dy81yyLYJDwMTifq24Oi/IslOslRrDSb3jwDggjz3Z0=
github.com/pelletier/go-toml/v2 v2.0.0-beta.8/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ=
github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v1.1.3 h1:xghbfqPkxzxP3C/f3n5DdpAbdKLj4ZE4BWQI362l53M=
github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo=
github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo=
github.com/spf13/cast v1.4.1 h1:s0hze+J0196ZfEMTs80N7UlFt0BDuQ7Q+JDnHiMWKdA=
github.com/spf13/cast v1.4.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v1.4.0 h1:y+wJpx64xcgO1V+RcnwW0LEHxTKRi2ZDPSBjWnrg88Q=
github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk=
github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/spf13/viper v1.11.0 h1:7OX/1FS6n7jHD1zGrZTM7WtY13ZELRyosK4k93oPr44=
github.com/spf13/viper v1.11.0/go.mod h1:djo0X/bA5+tYVoCn+C7cAYJGcVn/qYLFTG8gdUsX7Zk=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1 h1:5TQK59W5E3v0r2duFAb7P95B6hEeOyEnHRa8MjYSMTY=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
@@ -220,16 +222,22 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl
golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
@@ -238,20 +246,47 @@ golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -259,43 +294,127 @@ golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210331175145-43e1dd70ce54 h1:rF3Ohx8DRyl8h2zw9qojyLHLhrJpEMgyPOImREEryf0=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210331175145-43e1dd70ce54/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8=
golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE=
golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE=
google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE=
google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM=
google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc=
google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg=
google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE=
google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
@@ -305,24 +424,78 @@ google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA=
google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak=
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8=
google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4=
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/ini.v1 v1.66.4 h1:SsAcf+mM7mRZo2nJNGt8mZCjG8ZRaNGMURJw7BsIST4=
gopkg.in/ini.v1 v1.66.4/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

View File

@@ -23,13 +23,20 @@ elif [[ $NATIVE_ARCH == *"arm64"* || $NATIVE_ARCH == *"aarch64"* ]]; then
ARCH=arm64
elif [[ $NATIVE_ARCH == *"x86"* ]]; then
ARCH=386
elif [[ $NATIVE_ARCH == *"armv7"* ]]; then
ARCH=arm
else
echo "Could not determine Architecure automatically, please check the release page manually: https://github.com/cupcakearmy/autorestic/releases"
exit 1
fi
echo $ARCH
curl -s https://api.github.com/repos/cupcakearmy/autorestic/releases/latest \
if ! command -v bzip2 &>/dev/null; then
echo "Missing bzip2 command. Please install the bzip2 package for your system."
exit 1
fi
wget -qO - https://api.github.com/repos/cupcakearmy/autorestic/releases/latest \
| grep "browser_download_url.*_${OS}_${ARCH}" \
| cut -d : -f 2,3 \
| tr -d \" \

View File

@@ -9,20 +9,22 @@ import (
"strings"
"github.com/cupcakearmy/autorestic/internal/colors"
"github.com/cupcakearmy/autorestic/internal/flags"
)
type BackendRest struct {
User string `yaml:"user,omitempty"`
Password string `yaml:"password,omitempty"`
User string `mapstructure:"user,omitempty"`
Password string `mapstructure:"password,omitempty"`
}
type Backend struct {
name string
Type string `yaml:"type,omitempty"`
Path string `yaml:"path,omitempty"`
Key string `yaml:"key,omitempty"`
Env map[string]string `yaml:"env,omitempty"`
Rest BackendRest `yaml:"rest,omitempty"`
name string
Type string `mapstructure:"type,omitempty"`
Path string `mapstructure:"path,omitempty"`
Key string `mapstructure:"key,omitempty"`
Env map[string]string `mapstructure:"env,omitempty"`
Rest BackendRest `mapstructure:"rest,omitempty"`
Options Options `mapstructure:"options,omitempty"`
}
func GetBackend(name string) (Backend, bool) {
@@ -36,7 +38,7 @@ func (b Backend) generateRepo() (string, error) {
case "local":
return GetPathRelativeToConfig(b.Path)
case "rest":
parsed, err := url.Parse(b.Path)
parsed, err := url.Parse(os.ExpandEnv(b.Path))
if err != nil {
return "", err
}
@@ -57,12 +59,27 @@ func (b Backend) generateRepo() (string, error) {
func (b Backend) getEnv() (map[string]string, error) {
env := make(map[string]string)
env["RESTIC_PASSWORD"] = b.Key
// Key
if b.Key != "" {
env["RESTIC_PASSWORD"] = b.Key
}
// From config file
repo, err := b.generateRepo()
env["RESTIC_REPOSITORY"] = repo
for key, value := range b.Env {
env[strings.ToUpper(key)] = value
}
// From Envfile and passed as env
var prefix = "AUTORESTIC_" + strings.ToUpper(b.name) + "_"
for _, variable := range os.Environ() {
var splitted = strings.SplitN(variable, "=", 2)
if strings.HasPrefix(splitted[0], prefix) {
env[strings.TrimPrefix(splitted[0], prefix)] = splitted[1]
}
}
return env, err
}
@@ -84,32 +101,38 @@ func (b Backend) validate() error {
return fmt.Errorf(`Backend "%s" has no "path"`, b.name)
}
if b.Key == "" {
key := generateRandomKey()
b.Key = key
c := GetConfig()
tmp := c.Backends[b.name]
tmp.Key = key
c.Backends[b.name] = tmp
if err := c.SaveConfig(); err != nil {
return err
// Check if key is set in environment
env, _ := b.getEnv()
if _, found := env["RESTIC_PASSWORD"]; !found {
// No key set in config file or env => generate random key and save file
key := generateRandomKey()
b.Key = key
c := GetConfig()
tmp := c.Backends[b.name]
tmp.Key = key
c.Backends[b.name] = tmp
if err := c.SaveConfig(); err != nil {
return err
}
}
}
env, err := b.getEnv()
if err != nil {
return err
}
options := ExecuteOptions{Envs: env}
options := ExecuteOptions{Envs: env, Silent: true}
// Check if already initialized
_, err = ExecuteResticCommand(options, "snapshots")
cmd := []string{"check"}
cmd = append(cmd, combineBackendOptions("check", b)...)
_, _, err = ExecuteResticCommand(options, cmd...)
if err == nil {
return nil
} else {
// If not initialize
colors.Body.Printf("Initializing backend \"%s\"...\n", b.name)
out, err := ExecuteResticCommand(options, "init")
if VERBOSE {
colors.Faint.Println(out)
}
cmd := []string{"init"}
cmd = append(cmd, combineBackendOptions("init", b)...)
_, _, err := ExecuteResticCommand(options, cmd...)
return err
}
}
@@ -120,46 +143,62 @@ func (b Backend) Exec(args []string) error {
return err
}
options := ExecuteOptions{Envs: env}
out, err := ExecuteResticCommand(options, args...)
_, out, err := ExecuteResticCommand(options, args...)
if err != nil {
colors.Error.Println(out)
return err
}
if VERBOSE {
colors.Faint.Println(out)
}
return nil
}
func (b Backend) ExecDocker(l Location, args []string) (string, error) {
func (b Backend) ExecDocker(l Location, args []string) (int, string, error) {
env, err := b.getEnv()
if err != nil {
return "", err
return -1, "", err
}
volume := l.getVolumeName()
path, _ := l.getPath()
volume := l.From[0]
options := ExecuteOptions{
Command: "docker",
Envs: env,
}
dir := "/data"
args = append([]string{"restic"}, args...)
docker := []string{
"run", "--rm",
"--entrypoint", "ash",
"--workdir", path,
"--volume", volume + ":" + path,
"--workdir", dir,
"--volume", volume + ":" + dir,
}
// Use of docker host, not the container host
if hostname, err := os.Hostname(); err == nil {
docker = append(docker, "--hostname", hostname)
}
if b.Type == "local" {
switch b.Type {
case "local":
actual := env["RESTIC_REPOSITORY"]
docker = append(docker, "--volume", actual+":"+"/repo")
env["RESTIC_REPOSITORY"] = "/repo"
case "b2":
case "s3":
case "azure":
case "gs":
// No additional setup needed
case "rclone":
// Read host rclone config and mount it into the container
code, configFile, err := ExecuteCommand(ExecuteOptions{Command: "rclone"}, "config", "file")
if err != nil {
return code, "", err
}
splitted := strings.Split(strings.TrimSpace(configFile), "\n")
configFilePath := splitted[len(splitted)-1]
docker = append(docker, "--volume", configFilePath+":"+"/root/.config/rclone/rclone.conf:ro")
default:
return -1, "", fmt.Errorf("Backend type \"%s\" is not supported as volume endpoint", b.Type)
}
for key, value := range env {
docker = append(docker, "--env", key+"="+value)
}
docker = append(docker, "restic/restic", "-c", "restic "+strings.Join(args, " "))
out, err := ExecuteCommand(options, docker...)
return out, err
docker = append(docker, flags.DOCKER_IMAGE, "-c", strings.Join(args, " "))
return ExecuteCommand(options, docker...)
}

225
internal/backend_test.go Normal file
View File

@@ -0,0 +1,225 @@
package internal
import (
"fmt"
"os"
"testing"
"github.com/spf13/viper"
)
func TestGenerateRepo(t *testing.T) {
t.Run("empty backend", func(t *testing.T) {
b := Backend{
name: "empty backend",
Type: "",
}
_, err := b.generateRepo()
if err == nil {
t.Errorf("Error expected for empty backend type")
}
})
t.Run("local backend", func(t *testing.T) {
b := Backend{
name: "local backend",
Type: "local",
Path: "/foo/bar",
}
result, err := b.generateRepo()
if err != nil {
t.Errorf("unexpected error %v", err)
}
assertEqual(t, result, "/foo/bar")
})
t.Run("local backend with homedir prefix", func(t *testing.T) {
b := Backend{
name: "local backend",
Type: "local",
Path: "~/foo/bar",
}
result, err := b.generateRepo()
if err != nil {
t.Errorf("unexpected error %v", err)
}
assertEqual(t, result, fmt.Sprintf("%s/foo/bar", os.Getenv("HOME")))
})
t.Run("local backend with config file", func(t *testing.T) {
// config file path should always be present from initConfig
viper.SetConfigFile("/tmp/.autorestic.yml")
defer viper.Reset()
b := Backend{
name: "local backend",
Type: "local",
}
result, err := b.generateRepo()
if err != nil {
t.Errorf("unexpected error %v", err)
}
assertEqual(t, result, "/tmp")
})
t.Run("rest backend with valid path", func(t *testing.T) {
b := Backend{
name: "rest backend",
Type: "rest",
Path: "http://localhost:8000/foo",
}
result, err := b.generateRepo()
if err != nil {
t.Errorf("unexpected error %v", err)
}
assertEqual(t, result, "rest:http://localhost:8000/foo")
})
t.Run("rest backend with user", func(t *testing.T) {
b := Backend{
name: "rest backend",
Type: "rest",
Path: "http://localhost:8000/foo",
Rest: BackendRest{
User: "user",
Password: "",
},
}
result, err := b.generateRepo()
if err != nil {
t.Errorf("unexpected error %v", err)
}
assertEqual(t, result, "rest:http://user@localhost:8000/foo")
})
t.Run("rest backend with user and password", func(t *testing.T) {
b := Backend{
name: "rest backend",
Type: "rest",
Path: "http://localhost:8000/foo",
Rest: BackendRest{
User: "user",
Password: "pass",
},
}
result, err := b.generateRepo()
if err != nil {
t.Errorf("unexpected error %v", err)
}
assertEqual(t, result, "rest:http://user:pass@localhost:8000/foo")
})
backendTests := []struct {
name string
backend Backend
want string
}{
{name: "b2 backend", backend: Backend{name: "b2", Type: "b2", Path: "foo"}, want: "b2:foo"},
{name: "azure backend", backend: Backend{name: "azure", Type: "azure", Path: "foo"}, want: "azure:foo"},
{name: "gs backend", backend: Backend{name: "gs", Type: "gs", Path: "foo"}, want: "gs:foo"},
{name: "s3 backend", backend: Backend{name: "s3", Type: "s3", Path: "foo"}, want: "s3:foo"},
{name: "sftp backend", backend: Backend{name: "sftp", Type: "sftp", Path: "foo"}, want: "sftp:foo"},
{name: "rclone backend", backend: Backend{name: "rclone", Type: "rclone", Path: "foo"}, want: "rclone:foo"},
}
for _, tt := range backendTests {
t.Run(tt.name, func(t *testing.T) {
got, err := tt.backend.generateRepo()
if err != nil {
t.Errorf("unexpected error %v", err)
}
assertEqual(t, got, tt.want)
})
}
}
func TestGetEnv(t *testing.T) {
t.Run("env in key field", func(t *testing.T) {
b := Backend{
name: "",
Type: "local",
Path: "/foo/bar",
Key: "secret123",
}
result, err := b.getEnv()
if err != nil {
t.Errorf("unexpected error %v", err)
}
assertEqual(t, result["RESTIC_REPOSITORY"], "/foo/bar")
assertEqual(t, result["RESTIC_PASSWORD"], "secret123")
})
t.Run("env in config file", func(t *testing.T) {
b := Backend{
name: "",
Type: "local",
Path: "/foo/bar",
Env: map[string]string{
"B2_ACCOUNT_ID": "foo123",
"B2_ACCOUNT_KEY": "foo456",
},
}
result, err := b.getEnv()
if err != nil {
t.Errorf("unexpected error %v", err)
}
assertEqual(t, result["RESTIC_REPOSITORY"], "/foo/bar")
assertEqual(t, result["RESTIC_PASSWORD"], "")
assertEqual(t, result["B2_ACCOUNT_ID"], "foo123")
assertEqual(t, result["B2_ACCOUNT_KEY"], "foo456")
})
t.Run("env in Envfile or env vars", func(t *testing.T) {
// generate env variables
// TODO better way to teardown
defer os.Unsetenv("AUTORESTIC_FOO_RESTIC_PASSWORD")
defer os.Unsetenv("AUTORESTIC_FOO_B2_ACCOUNT_ID")
defer os.Unsetenv("AUTORESTIC_FOO_B2_ACCOUNT_KEY")
os.Setenv("AUTORESTIC_FOO_RESTIC_PASSWORD", "secret123")
os.Setenv("AUTORESTIC_FOO_B2_ACCOUNT_ID", "foo123")
os.Setenv("AUTORESTIC_FOO_B2_ACCOUNT_KEY", "foo456")
b := Backend{
name: "foo",
Type: "local",
Path: "/foo/bar",
}
result, err := b.getEnv()
if err != nil {
t.Errorf("unexpected error %v", err)
}
assertEqual(t, result["RESTIC_REPOSITORY"], "/foo/bar")
assertEqual(t, result["RESTIC_PASSWORD"], "secret123")
assertEqual(t, result["B2_ACCOUNT_ID"], "foo123")
assertEqual(t, result["B2_ACCOUNT_KEY"], "foo456")
})
}
func TestValidate(t *testing.T) {
t.Run("no type given", func(t *testing.T) {
b := Backend{
name: "foo",
Type: "",
Path: "/foo/bar",
}
err := b.validate()
if err == nil {
t.Error("expected to get error")
}
assertEqual(t, err.Error(), "Backend \"foo\" has no \"type\"")
})
t.Run("no path given", func(t *testing.T) {
b := Backend{
name: "foo",
Type: "local",
Path: "",
}
err := b.validate()
if err == nil {
t.Error("expected to get error")
}
assertEqual(t, err.Error(), "Backend \"foo\" has no \"path\"")
})
}

View File

@@ -16,6 +16,7 @@ import (
"github.com/blang/semver/v4"
"github.com/cupcakearmy/autorestic/internal"
"github.com/cupcakearmy/autorestic/internal/colors"
"github.com/cupcakearmy/autorestic/internal/flags"
)
const INSTALL_PATH = "/usr/local/bin"
@@ -47,11 +48,11 @@ func dlJSON(url string) (GithubRelease, error) {
func Uninstall(restic bool) error {
if err := os.Remove(path.Join(INSTALL_PATH, "autorestic")); err != nil {
colors.Error.Println(err)
return err
}
if restic {
if err := os.Remove(path.Join(INSTALL_PATH, "restic")); err != nil {
colors.Error.Println(err)
return err
}
}
return nil
@@ -79,13 +80,31 @@ func downloadAndInstallAsset(body GithubRelease, name string) error {
return err
}
defer tmp.Close()
tmp.Chmod(0755)
io.Copy(tmp, bz)
if err := tmp.Chmod(0755); err != nil {
return err
}
if _, err := io.Copy(tmp, bz); err != nil {
return err
}
to := path.Join(INSTALL_PATH, name)
os.Remove(to) // Delete if current, ignore error if file does not exits.
defer os.Remove(tmp.Name()) // Cleanup temporary file after thread exits
if err := os.Rename(tmp.Name(), to); err != nil {
return nil
colors.Error.Printf("os.Rename() failed (%v), retrying with io.Copy()\n", err.Error())
var src *os.File
var dst *os.File
if src, err = os.Open(tmp.Name()); err != nil {
return err
}
if dst, err = os.Create(to); err != nil {
return err
}
if _, err := io.Copy(dst, src); err != nil {
return err
}
if err := os.Chmod(to, 0755); err != nil {
return err
}
}
colors.Success.Printf("Successfully installed '%s' under %s\n", name, INSTALL_PATH)
@@ -110,10 +129,9 @@ func InstallRestic() error {
}
func upgradeRestic() error {
out, err := internal.ExecuteCommand(internal.ExecuteOptions{
Command: "restic",
_, _, err := internal.ExecuteCommand(internal.ExecuteOptions{
Command: flags.RESTIC_BIN,
}, "self-update")
colors.Faint.Println(out)
return err
}
@@ -121,9 +139,11 @@ func Upgrade(restic bool) error {
// Upgrade restic
if restic {
if err := InstallRestic(); err != nil {
colors.Error.Println(err)
return err
}
if err := upgradeRestic(); err != nil {
return err
}
upgradeRestic()
}
// Upgrade self
@@ -140,7 +160,9 @@ func Upgrade(restic bool) error {
return err
}
if current.LT(latest) {
downloadAndInstallAsset(body, "autorestic")
if err := downloadAndInstallAsset(body, "autorestic"); err != nil {
return err
}
colors.Success.Println("Updated autorestic")
} else {
colors.Body.Println("Already up to date")

View File

@@ -2,44 +2,95 @@ package internal
import (
"fmt"
"os"
"path"
"path/filepath"
"strings"
"sync"
"github.com/cupcakearmy/autorestic/internal/colors"
"github.com/cupcakearmy/autorestic/internal/flags"
"github.com/cupcakearmy/autorestic/internal/lock"
"github.com/joho/godotenv"
"github.com/mitchellh/go-homedir"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
const VERSION = "1.0.9"
const VERSION = "1.7.9"
var CI bool = false
var VERBOSE bool = false
var CRON_LEAN bool = false
type OptionMap map[string][]interface{}
type Options map[string]OptionMap
type Config struct {
Locations map[string]Location `yaml:"locations"`
Backends map[string]Backend `yaml:"backends"`
Version string `mapstructure:"version"`
Extras interface{} `mapstructure:"extras"`
Locations map[string]Location `mapstructure:"locations"`
Backends map[string]Backend `mapstructure:"backends"`
Global Options `mapstructure:"global"`
}
var once sync.Once
var config *Config
func exitConfig(err error, msg string) {
if err != nil {
colors.Error.Println(err)
}
if msg != "" {
colors.Error.Println(msg)
}
lock.Unlock()
os.Exit(1)
}
func GetConfig() *Config {
if config == nil {
once.Do(func() {
if err := viper.ReadInConfig(); err == nil {
if !CRON_LEAN {
colors.Faint.Println("Using config file:", viper.ConfigFileUsed())
absConfig, _ := filepath.Abs(viper.ConfigFileUsed())
if !flags.CRON_LEAN {
colors.Faint.Println("Using config: \t", absConfig)
}
// Load env file
envFile := filepath.Join(filepath.Dir(absConfig), ".autorestic.env")
err = godotenv.Load(envFile)
if err == nil && !flags.CRON_LEAN {
colors.Faint.Println("Using env:\t", envFile)
}
} else {
return
text := err.Error()
if strings.Contains(text, "no such file or directory") {
cfgFileName := ".autorestic"
colors.Error.Println(
fmt.Sprintf(
"cannot find configuration file '%s.yml' or '%s.yaml'.",
cfgFileName, cfgFileName))
} else {
colors.Error.Println("could not load config file\n" + text)
}
os.Exit(1)
}
var versionConfig interface{}
viper.UnmarshalKey("version", &versionConfig)
if versionConfig == nil {
exitConfig(nil, "no version specified in config file. please see docs on how to migrate")
}
version, ok := versionConfig.(int)
if !ok {
exitConfig(nil, "version specified in config file is not an int")
} else {
// Check for version
if version != 2 {
exitConfig(nil, "unsupported config version number. please check the docs for migration\nhttps://autorestic.vercel.app/migration/")
}
}
config = &Config{}
if err := viper.UnmarshalExact(config); err != nil {
panic(err)
exitConfig(err, "Could not parse config file!")
}
})
}
@@ -63,7 +114,11 @@ func (c *Config) Describe() {
var tmp string
colors.PrimaryPrint(`Location: "%s"`, name)
colors.PrintDescription("From", l.From)
tmp = ""
for _, path := range l.From {
tmp += fmt.Sprintf("\t%s %s\n", colors.Success.Sprint("←"), path)
}
colors.PrintDescription("From", tmp)
tmp = ""
for _, to := range l.To {
@@ -75,21 +130,22 @@ func (c *Config) Describe() {
colors.PrintDescription("Cron", l.Cron)
}
after, before := len(l.Hooks.After), len(l.Hooks.Before)
if after+before > 0 {
tmp = ""
if before > 0 {
tmp += "\tBefore"
for _, cmd := range l.Hooks.Before {
tmp += colors.Faint.Sprintf("\n\t ▶ %s", cmd)
}
}
if after > 0 {
tmp += "\n\tAfter"
for _, cmd := range l.Hooks.After {
tmp = ""
hooks := map[string][]string{
"Before": l.Hooks.Before,
"After": l.Hooks.After,
"Failure": l.Hooks.Failure,
"Success": l.Hooks.Success,
}
for hook, commands := range hooks {
if len(commands) > 0 {
tmp += "\n\t" + hook
for _, cmd := range commands {
tmp += colors.Faint.Sprintf("\n\t ▶ %s", cmd)
}
}
}
if tmp != "" {
colors.PrintDescription("Hooks", tmp)
}
@@ -129,7 +185,7 @@ func CheckConfig() error {
return fmt.Errorf("config could not be loaded/found")
}
if !CheckIfResticIsCallable() {
return fmt.Errorf(`restic was not found. Install either with "autorestic install" or manually`)
return fmt.Errorf(`%s was not found. Install either with "autorestic install" or manually`, flags.RESTIC_BIN)
}
for name, backend := range c.Backends {
backend.name = name
@@ -139,7 +195,7 @@ func CheckConfig() error {
}
for name, location := range c.Locations {
location.name = name
if err := location.validate(c); err != nil {
if err := location.validate(); err != nil {
return err
}
}
@@ -170,20 +226,18 @@ func GetAllOrSelected(cmd *cobra.Command, backends bool) ([]string, error) {
selected, _ = cmd.Flags().GetStringSlice("location")
}
for _, s := range selected {
found := false
var splitted = strings.Split(s, "@")
for _, l := range list {
if l == s {
found = true
break
if l == splitted[0] {
goto found
}
}
if !found {
if backends {
return nil, fmt.Errorf("invalid backend \"%s\"", s)
} else {
return nil, fmt.Errorf("invalid location \"%s\"", s)
}
if backends {
return nil, fmt.Errorf("invalid backend \"%s\"", s)
} else {
return nil, fmt.Errorf("invalid location \"%s\"", s)
}
found:
}
if len(selected) == 0 {
@@ -212,10 +266,61 @@ func (c *Config) SaveConfig() error {
if err := CopyFile(file, file+".old"); err != nil {
return err
}
colors.Secondary.Println("Saved a backup copy of your file next the the original.")
colors.Secondary.Println("Saved a backup copy of your file next to the original.")
viper.Set("backends", c.Backends)
viper.Set("locations", c.Locations)
return viper.WriteConfig()
}
func optionToString(option string) string {
if !strings.HasPrefix(option, "-") {
return "--" + option
}
return option
}
func appendOptionsToSlice(str *[]string, options OptionMap) {
for key, values := range options {
for _, value := range values {
// Bool
asBool, ok := value.(bool)
if ok && asBool {
*str = append(*str, optionToString(key))
continue
}
*str = append(*str, optionToString(key), fmt.Sprint(value))
}
}
}
func getOptions(options Options, keys []string) []string {
var selected []string
for _, key := range keys {
appendOptionsToSlice(&selected, options[key])
}
return selected
}
func combineBackendOptions(key string, b Backend) []string {
// Priority: backend > global
var options []string
gFlags := getOptions(GetConfig().Global, []string{key})
bFlags := getOptions(b.Options, []string{"all", key})
options = append(options, gFlags...)
options = append(options, bFlags...)
return options
}
func combineAllOptions(key string, l Location, b Backend) []string {
// Priority: location > backend > global
var options []string
gFlags := getOptions(GetConfig().Global, []string{key})
bFlags := getOptions(b.Options, []string{"all", key})
lFlags := getOptions(l.Options, []string{"all", key})
options = append(options, gFlags...)
options = append(options, bFlags...)
options = append(options, lFlags...)
return options
}

164
internal/config_test.go Normal file
View File

@@ -0,0 +1,164 @@
package internal
import (
"reflect"
"strconv"
"strings"
"testing"
)
func TestOptionToString(t *testing.T) {
t.Run("no prefix", func(t *testing.T) {
opt := "test"
result := optionToString(opt)
assertEqual(t, result, "--test")
})
t.Run("single prefix", func(t *testing.T) {
opt := "-test"
result := optionToString(opt)
assertEqual(t, result, "-test")
})
t.Run("double prefix", func(t *testing.T) {
opt := "--test"
result := optionToString(opt)
assertEqual(t, result, "--test")
})
}
func TestAppendOneOptionToSlice(t *testing.T) {
t.Run("string flag", func(t *testing.T) {
result := []string{}
optionMap := OptionMap{"string-flag": []interface{}{"/root"}}
appendOptionsToSlice(&result, optionMap)
expected := []string{
"--string-flag", "/root",
}
assertSliceEqual(t, result, expected)
})
t.Run("bool flag", func(t *testing.T) {
result := []string{}
optionMap := OptionMap{"boolean-flag": []interface{}{true}}
appendOptionsToSlice(&result, optionMap)
expected := []string{
"--boolean-flag",
}
assertSliceEqual(t, result, expected)
})
t.Run("int flag", func(t *testing.T) {
result := []string{}
optionMap := OptionMap{"int-flag": []interface{}{123}}
appendOptionsToSlice(&result, optionMap)
expected := []string{
"--int-flag", "123",
}
assertSliceEqual(t, result, expected)
})
}
func TestAppendMultipleOptionsToSlice(t *testing.T) {
result := []string{}
optionMap := OptionMap{
"string-flag": []interface{}{"/root"},
"int-flag": []interface{}{123},
}
appendOptionsToSlice(&result, optionMap)
expected := []string{
"--string-flag", "/root",
"--int-flag", "123",
}
if len(result) != len(expected) {
t.Errorf("got length %d, want length %d", len(result), len(expected))
}
// checks that expected option comes after flag, regardless of key order in map
for i, v := range expected {
v = strings.TrimPrefix(v, "--")
if value, ok := optionMap[v]; ok {
if val, ok := value[0].(int); ok {
if expected[i+1] != strconv.Itoa(val) {
t.Errorf("Flags and options order are mismatched. got %v, want %v", result, expected)
}
}
}
}
}
func TestAppendOptionWithMultipleValuesToSlice(t *testing.T) {
result := []string{}
optionMap := OptionMap{
"string-flag": []interface{}{"/root", "/bin"},
}
appendOptionsToSlice(&result, optionMap)
expected := []string{
"--string-flag", "/root",
"--string-flag", "/bin",
}
assertSliceEqual(t, result, expected)
}
func TestGetOptionsOneKey(t *testing.T) {
optionMap := OptionMap{
"string-flag": []interface{}{"/root"},
}
options := Options{"backend": optionMap}
keys := []string{"backend"}
result := getOptions(options, keys)
expected := []string{
"--string-flag", "/root",
}
assertSliceEqual(t, result, expected)
}
func TestGetOptionsMultipleKeys(t *testing.T) {
firstOptionMap := OptionMap{
"string-flag": []interface{}{"/root"},
}
secondOptionMap := OptionMap{
"boolean-flag": []interface{}{true},
"int-flag": []interface{}{123},
}
options := Options{
"all": firstOptionMap,
"forget": secondOptionMap,
}
keys := []string{"all", "forget"}
result := getOptions(options, keys)
expected := []string{
"--string-flag", "/root",
"--boolean-flag",
"--int-flag", "123",
}
reflect.DeepEqual(result, expected)
}
func assertEqual[T comparable](t testing.TB, result, expected T) {
t.Helper()
if result != expected {
t.Errorf("got %v, want %v", result, expected)
}
}
func assertSliceEqual(t testing.TB, result, expected []string) {
t.Helper()
if len(result) != len(expected) {
t.Errorf("got length %d, want length %d", len(result), len(expected))
}
for i := range result {
assertEqual(t, result[i], expected[i])
}
}

9
internal/flags/flags.go Normal file
View File

@@ -0,0 +1,9 @@
package flags
var (
CI bool = false
VERBOSE bool = false
CRON_LEAN bool = false
RESTIC_BIN string
DOCKER_IMAGE string
)

View File

@@ -9,34 +9,49 @@ import (
"time"
"github.com/cupcakearmy/autorestic/internal/colors"
"github.com/cupcakearmy/autorestic/internal/flags"
"github.com/cupcakearmy/autorestic/internal/lock"
"github.com/cupcakearmy/autorestic/internal/metadata"
"github.com/robfig/cron"
)
type LocationType string
const (
TypeLocal LocationType = "local"
TypeVolume LocationType = "volume"
VolumePrefix string = "volume:"
TypeLocal LocationType = "local"
TypeVolume LocationType = "volume"
)
type HookArray = []string
type LocationForgetOption string
const (
LocationForgetYes LocationForgetOption = "yes"
LocationForgetNo LocationForgetOption = "no"
LocationForgetPrune LocationForgetOption = "prune"
)
type Hooks struct {
Before HookArray `yaml:"before"`
After HookArray `yaml:"after"`
Dir string `mapstructure:"dir"`
Before HookArray `mapstructure:"before,omitempty"`
After HookArray `mapstructure:"after,omitempty"`
Success HookArray `mapstructure:"success,omitempty"`
Failure HookArray `mapstructure:"failure,omitempty"`
}
type Options map[string]map[string][]string
type LocationCopy = map[string][]string
type Location struct {
name string `yaml:",omitempty"`
From string `yaml:"from,omitempty"`
To []string `yaml:"to,omitempty"`
Hooks Hooks `yaml:"hooks,omitempty"`
Cron string `yaml:"cron,omitempty"`
Options Options `yaml:"options,omitempty"`
name string `mapstructure:",omitempty"`
From []string `mapstructure:"from,omitempty"`
Type string `mapstructure:"type,omitempty"`
To []string `mapstructure:"to,omitempty"`
Hooks Hooks `mapstructure:"hooks,omitempty"`
Cron string `mapstructure:"cron,omitempty"`
Options Options `mapstructure:"options,omitempty"`
ForgetOption LocationForgetOption `mapstructure:"forget,omitempty"`
CopyOption LocationCopy `mapstructure:"copy,omitempty"`
}
func GetLocation(name string) (Location, bool) {
@@ -45,166 +60,275 @@ func GetLocation(name string) (Location, bool) {
return l, ok
}
func (l Location) validate(c *Config) error {
if l.From == "" {
func (l Location) validate() error {
if len(l.From) == 0 {
return fmt.Errorf(`Location "%s" is missing "from" key`, l.name)
}
if l.getType() == TypeLocal {
if from, err := GetPathRelativeToConfig(l.From); err != nil {
return err
} else {
if stat, err := os.Stat(from); err != nil {
t, err := l.getType()
if err != nil {
return err
}
switch t {
case TypeLocal:
for _, path := range l.From {
if from, err := GetPathRelativeToConfig(path); err != nil {
return err
} else {
if !stat.IsDir() {
return fmt.Errorf("\"%s\" is not valid directory for location \"%s\"", from, l.name)
if _, err := os.Stat(from); err != nil {
return err
}
}
}
case TypeVolume:
if len(l.From) > 1 {
return fmt.Errorf(`location "%s" has more than one docker volume`, l.name)
}
}
if len(l.To) == 0 {
return fmt.Errorf(`Location "%s" has no "to" targets`, l.name)
return fmt.Errorf(`location "%s" has no "to" targets`, l.name)
}
// Check if backends are all valid
for _, to := range l.To {
_, ok := GetBackend(to)
if !ok {
return fmt.Errorf("invalid backend `%s`", to)
return fmt.Errorf(`location "%s" has an invalid backend "%s"`, l.name, to)
}
}
// Check copy option
for copyFrom, copyTo := range l.CopyOption {
if _, ok := GetBackend(copyFrom); !ok {
return fmt.Errorf(`location "%s" has an invalid backend "%s" in copy option`, l.name, copyFrom)
}
if !ArrayContains(l.To, copyFrom) {
return fmt.Errorf(`location "%s" has an invalid copy from "%s"`, l.name, copyFrom)
}
for _, copyToTarget := range copyTo {
if _, ok := GetBackend(copyToTarget); !ok {
return fmt.Errorf(`location "%s" has an invalid backend "%s" in copy option`, l.name, copyToTarget)
}
if ArrayContains(l.To, copyToTarget) {
return fmt.Errorf(`location "%s" cannot copy to "%s" as it's already a target`, l.name, copyToTarget)
}
}
}
// Check if forget type is correct
if l.ForgetOption != "" {
if l.ForgetOption != LocationForgetYes && l.ForgetOption != LocationForgetNo && l.ForgetOption != LocationForgetPrune {
return fmt.Errorf("invalid value for forget option: %s", l.ForgetOption)
}
}
return nil
}
func (l Location) getOptions(key string) []string {
var options []string
saved := l.Options[key]
for k, values := range saved {
for _, value := range values {
options = append(options, fmt.Sprintf("--%s", k), value)
}
}
return options
}
func ExecuteHooks(commands []string, options ExecuteOptions) error {
func (l Location) ExecuteHooks(commands []string, options ExecuteOptions) error {
if len(commands) == 0 {
return nil
}
if l.Hooks.Dir != "" {
if dir, err := GetPathRelativeToConfig(l.Hooks.Dir); err != nil {
return err
} else {
options.Dir = dir
}
}
colors.Secondary.Println("\nRunning hooks")
for _, command := range commands {
colors.Body.Println("> " + command)
out, err := ExecuteCommand(options, "-c", command)
_, out, err := ExecuteCommand(options, "-c", command)
if err != nil {
colors.Error.Println(out)
return err
}
if VERBOSE {
colors.Faint.Println(out)
}
}
colors.Body.Println("")
return nil
}
func (l Location) getType() LocationType {
if strings.HasPrefix(l.From, VolumePrefix) {
return TypeVolume
func (l Location) getType() (LocationType, error) {
t := strings.ToLower(l.Type)
if t == "" || t == "local" {
return TypeLocal, nil
} else if t == "volume" {
return TypeVolume, nil
}
return TypeLocal
return "", fmt.Errorf("invalid location type \"%s\"", l.Type)
}
func (l Location) getVolumeName() string {
return strings.TrimPrefix(l.From, VolumePrefix)
func buildTag(parts ...string) string {
parts = append([]string{"ar"}, parts...)
return strings.Join(parts, ":")
}
func (l Location) getPath() (string, error) {
t := l.getType()
switch t {
case TypeLocal:
if path, err := GetPathRelativeToConfig(l.From); err != nil {
return "", err
} else {
return path, nil
}
case TypeVolume:
return "/volume/" + l.name + "/" + l.getVolumeName(), nil
}
return "", fmt.Errorf("could not get path for location \"%s\"", l.name)
func (l Location) getLocationTags() string {
return buildTag("location", l.name)
}
func (l Location) Backup(cron bool) error {
func (l Location) Backup(cron bool, specificBackend string) []error {
var errors []error
var backends []string
colors.PrimaryPrint(" Backing up location \"%s\" ", l.name)
t := l.getType()
t, err := l.getType()
if err != nil {
errors = append(errors, err)
return errors
}
cwd, _ := GetPathRelativeToConfig(".")
options := ExecuteOptions{
Command: "bash",
Dir: cwd,
Envs: map[string]string{
"AUTORESTIC_LOCATION": l.name,
},
}
if t == TypeLocal {
dir, _ := GetPathRelativeToConfig(l.From)
options.Dir = dir
if err := l.validate(); err != nil {
errors = append(errors, err)
goto after
}
// Hooks
if err := ExecuteHooks(l.Hooks.Before, options); err != nil {
return err
if err := l.ExecuteHooks(l.Hooks.Before, options); err != nil {
errors = append(errors, err)
goto after
}
// Backup
for _, to := range l.To {
if specificBackend == "" {
backends = l.To
} else {
if l.hasBackend(specificBackend) {
backends = []string{specificBackend}
} else {
errors = append(errors, fmt.Errorf("backup location \"%s\" has no backend \"%s\"", l.name, specificBackend))
return errors
}
}
for i, to := range backends {
backend, _ := GetBackend(to)
colors.Secondary.Printf("Backend: %s\n", backend.name)
env, err := backend.getEnv()
if err != nil {
return nil
errors = append(errors, err)
continue
}
flags := l.getOptions("backup")
cmd := []string{"backup"}
cmd = append(cmd, flags...)
cmd = append(cmd, combineAllOptions("backup", l, backend)...)
if cron {
cmd = append(cmd, "--tag", "cron")
cmd = append(cmd, "--tag", buildTag("cron"))
}
cmd = append(cmd, ".")
cmd = append(cmd, "--tag", l.getLocationTags())
backupOptions := ExecuteOptions{
Dir: options.Dir,
Envs: env,
}
var code int = 0
var out string
switch t {
case TypeLocal:
out, err = ExecuteResticCommand(backupOptions, cmd...)
for _, from := range l.From {
path, err := GetPathRelativeToConfig(from)
if err != nil {
errors = append(errors, err)
goto after
}
cmd = append(cmd, path)
}
code, out, err = ExecuteResticCommand(backupOptions, cmd...)
case TypeVolume:
out, err = backend.ExecDocker(l, cmd)
ok := CheckIfVolumeExists(l.From[0])
if !ok {
errors = append(errors, fmt.Errorf("volume \"%s\" does not exist", l.From[0]))
continue
}
cmd = append(cmd, "/data")
code, out, err = backend.ExecDocker(l, cmd)
}
// Extract metadata
md := metadata.ExtractMetadataFromBackupLog(out)
md.ExitCode = fmt.Sprint(code)
mdEnv := metadata.MakeEnvFromMetadata(&md)
for k, v := range mdEnv {
options.Envs[k+"_"+fmt.Sprint(i)] = v
options.Envs[k+"_"+strings.ToUpper(backend.name)] = v
}
// If error save it and continue
if err != nil {
colors.Error.Println(out)
return err
errors = append(errors, fmt.Errorf("%s@%s:\n%s%s", l.name, backend.name, out, err))
continue
}
if VERBOSE {
colors.Faint.Println(out)
// Copy
if md.SnapshotID != "" {
for copyFrom, copyTo := range l.CopyOption {
b1, _ := GetBackend(copyFrom)
for _, copyToTarget := range copyTo {
b2, _ := GetBackend(copyToTarget)
colors.Secondary.Println("Copying " + copyFrom + " → " + copyToTarget)
env, _ := b1.getEnv()
env2, _ := b2.getEnv()
// Add the second repo to the env with a "2" suffix
for k, v := range env2 {
env[k+"2"] = v
}
_, _, err := ExecuteResticCommand(ExecuteOptions{
Envs: env,
}, "copy", md.SnapshotID)
if err != nil {
errors = append(errors, err)
}
}
}
}
}
// After hooks
if err := ExecuteHooks(l.Hooks.After, options); err != nil {
return err
if err := l.ExecuteHooks(l.Hooks.After, options); err != nil {
errors = append(errors, err)
}
colors.Success.Println("Done")
return nil
after:
var commands []string
var isSuccess = len(errors) == 0
if isSuccess {
commands = l.Hooks.Success
} else {
commands = l.Hooks.Failure
}
if err := l.ExecuteHooks(commands, options); err != nil {
errors = append(errors, err)
}
// Forget and optionally prune
if isSuccess && l.ForgetOption != "" && l.ForgetOption != LocationForgetNo {
err := l.Forget(l.ForgetOption == LocationForgetPrune, false)
if err != nil {
errors = append(errors, err)
}
}
if len(errors) == 0 {
colors.Success.Println("Done")
}
return errors
}
func (l Location) Forget(prune bool, dry bool) error {
colors.PrimaryPrint("Forgetting for location \"%s\"", l.name)
path, err := l.getPath()
if err != nil {
return err
backendsToForget := l.To
for _, copyBackends := range l.CopyOption {
backendsToForget = append(backendsToForget, copyBackends...)
}
for _, to := range l.To {
for _, to := range backendsToForget {
backend, _ := GetBackend(to)
colors.Secondary.Printf("For backend \"%s\"\n", backend.name)
env, err := backend.getEnv()
@@ -214,19 +338,15 @@ func (l Location) Forget(prune bool, dry bool) error {
options := ExecuteOptions{
Envs: env,
}
flags := l.getOptions("forget")
cmd := []string{"forget", "--path", path}
cmd := []string{"forget", "--tag", l.getLocationTags()}
if prune {
cmd = append(cmd, "--prune")
}
if dry {
cmd = append(cmd, "--dry-run")
}
cmd = append(cmd, flags...)
out, err := ExecuteResticCommand(options, cmd...)
if VERBOSE {
colors.Faint.Println(out)
}
cmd = append(cmd, combineAllOptions("forget", l, backend)...)
_, _, err = ExecuteResticCommand(options, cmd...)
if err != nil {
return err
}
@@ -244,28 +364,37 @@ func (l Location) hasBackend(backend string) bool {
return false
}
func (l Location) Restore(to, from string, force bool) error {
func buildRestoreCommand(l Location, to string, snapshot string, options []string) []string {
base := []string{"restore", "--target", to, "--tag", l.getLocationTags(), snapshot}
base = append(base, options...)
return base
}
func (l Location) Restore(to, from string, force bool, snapshot string, options []string) error {
if from == "" {
from = l.To[0]
} else if !l.hasBackend(from) {
return fmt.Errorf("invalid backend: \"%s\"", from)
}
to, err := filepath.Abs(to)
if snapshot == "" {
snapshot = "latest"
}
colors.PrimaryPrint("Restoring location \"%s\"", l.name)
backend, _ := GetBackend(from)
colors.Secondary.Printf("Restoring %s@%s → %s\n", snapshot, backend.name, to)
t, err := l.getType()
if err != nil {
return err
}
colors.PrimaryPrint("Restoring location \"%s\"", l.name)
backend, _ := GetBackend(from)
path, err := l.getPath()
if err != nil {
return nil
}
colors.Secondary.Println("Restoring lastest snapshot")
colors.Body.Printf("%s → %s.\n", from, path)
switch l.getType() {
switch t {
case TypeLocal:
to, err = filepath.Abs(to)
if err != nil {
return err
}
// Check if target is empty
if !force {
notEmptyError := fmt.Errorf("target %s is not empty", to)
@@ -284,9 +413,9 @@ func (l Location) Restore(to, from string, force bool) error {
}
}
}
err = backend.Exec([]string{"restore", "--target", to, "--path", path, "latest"})
err = backend.Exec(buildRestoreCommand(l, to, snapshot, options))
case TypeVolume:
_, err = backend.ExecDocker(l, []string{"restore", "--target", ".", "--path", path, "latest"})
_, _, err = backend.ExecDocker(l, buildRestoreCommand(l, "/", snapshot, options))
}
if err != nil {
return err
@@ -309,9 +438,9 @@ func (l Location) RunCron() error {
now := time.Now()
if now.After(next) {
lock.SetCron(l.name, now.Unix())
l.Backup(true)
l.Backup(true, "")
} else {
if !CRON_LEAN {
if !flags.CRON_LEAN {
colors.Body.Printf("Skipping \"%s\", not due yet.\n", l.name)
}
}

93
internal/location_test.go Normal file
View File

@@ -0,0 +1,93 @@
package internal
import "testing"
func TestGetType(t *testing.T) {
t.Run("TypeLocal", func(t *testing.T) {
l := Location{
Type: "local",
}
result, err := l.getType()
if err != nil {
t.Errorf("unexpected error: %v", err)
}
assertEqual(t, result, TypeLocal)
})
t.Run("TypeVolume", func(t *testing.T) {
l := Location{
Type: "volume",
}
result, err := l.getType()
if err != nil {
t.Errorf("unexpected error: %v", err)
}
assertEqual(t, result, TypeVolume)
})
t.Run("Empty type", func(t *testing.T) {
l := Location{
Type: "",
}
result, err := l.getType()
if err != nil {
t.Errorf("unexpected error: %v", err)
}
assertEqual(t, result, TypeLocal)
})
t.Run("Invalid type", func(t *testing.T) {
l := Location{
Type: "foo",
}
_, err := l.getType()
if err == nil {
t.Error("expected error")
}
})
}
func TestBuildTag(t *testing.T) {
result := buildTag("foo", "bar")
expected := "ar:foo:bar"
assertEqual(t, result, expected)
}
func TestGetLocationTags(t *testing.T) {
l := Location{
name: "foo",
}
result := l.getLocationTags()
expected := "ar:location:foo"
assertEqual(t, result, expected)
}
func TestHasBackend(t *testing.T) {
t.Run("backend present", func(t *testing.T) {
l := Location{
name: "foo",
To: []string{"foo", "bar"},
}
result := l.hasBackend("foo")
assertEqual(t, result, true)
})
t.Run("backend absent", func(t *testing.T) {
l := Location{
name: "foo",
To: []string{"bar", "baz"},
}
result := l.hasBackend("foo")
assertEqual(t, result, false)
})
}
func TestBuildRestoreCommand(t *testing.T) {
l := Location{
name: "foo",
}
result := buildRestoreCommand(l, "to", "snapshot", []string{"options"})
expected := []string{"restore", "--target", "to", "--tag", "ar:location:foo", "snapshot", "options"}
assertSliceEqual(t, result, expected)
}

View File

@@ -6,6 +6,7 @@ import (
"sync"
"github.com/cupcakearmy/autorestic/internal/colors"
"github.com/cupcakearmy/autorestic/internal/flags"
"github.com/spf13/viper"
)
@@ -13,14 +14,25 @@ var lock *viper.Viper
var file string
var once sync.Once
const (
RUNNING = "running"
)
func getLock() *viper.Viper {
if lock == nil {
once.Do(func() {
lock = viper.New()
lock.SetDefault("running", false)
p := path.Dir(viper.ConfigFileUsed())
file = path.Join(p, ".autorestic.lock.yml")
p := viper.ConfigFileUsed()
if p == "" {
colors.Error.Println("cannot lock before reading config location")
os.Exit(1)
}
file = path.Join(path.Dir(p), ".autorestic.lock.yml")
if !flags.CRON_LEAN {
colors.Faint.Println("Using lock:\t", file)
}
lock.SetConfigFile(file)
lock.SetConfigType("yml")
lock.ReadInConfig()
@@ -29,36 +41,38 @@ func getLock() *viper.Viper {
return lock
}
func setLock(locked bool) error {
func setLockValue(key string, value interface{}) (*viper.Viper, error) {
lock := getLock()
if locked {
running := lock.GetBool("running")
if running {
if key == RUNNING {
value := value.(bool)
if value && lock.GetBool(key) {
colors.Error.Println("an instance is already running. exiting")
os.Exit(1)
}
}
lock.Set("running", locked)
lock.Set(key, value)
if err := lock.WriteConfigAs(file); err != nil {
return err
return nil, err
}
return nil
return lock, nil
}
func GetCron(location string) int64 {
lock := getLock()
return lock.GetInt64("cron." + location)
return getLock().GetInt64("cron." + location)
}
func SetCron(location string, value int64) {
lock.Set("cron."+location, value)
lock.WriteConfigAs(file)
setLockValue("cron."+location, value)
}
func Lock() error {
return setLock(true)
_, err := setLockValue(RUNNING, true)
return err
}
func Unlock() error {
return setLock(false)
_, err := setLockValue(RUNNING, false)
return err
}

114
internal/lock/lock_test.go Normal file
View File

@@ -0,0 +1,114 @@
package lock
import (
"log"
"os"
"os/exec"
"strconv"
"testing"
"github.com/spf13/viper"
)
var testDirectory = "autorestic_test_tmp"
// All tests must share the same lock file as it is only initialized once
func setup(t *testing.T) {
d, err := os.MkdirTemp("", testDirectory)
if err != nil {
log.Fatalf("error creating temp dir: %v", err)
return
}
// set config file location
viper.SetConfigFile(d + "/.autorestic.yml")
t.Cleanup(func() {
os.RemoveAll(d)
viper.Reset()
})
}
func TestLock(t *testing.T) {
setup(t)
t.Run("getLock", func(t *testing.T) {
result := getLock().GetBool(RUNNING)
if result {
t.Errorf("got %v, want %v", result, false)
}
})
t.Run("lock", func(t *testing.T) {
lock, err := setLockValue(RUNNING, true)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
result := lock.GetBool(RUNNING)
if !result {
t.Errorf("got %v, want %v", result, true)
}
})
t.Run("unlock", func(t *testing.T) {
lock, err := setLockValue(RUNNING, false)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
result := lock.GetBool(RUNNING)
if result {
t.Errorf("got %v, want %v", result, false)
}
})
// locking a locked instance exits the instance
// this trick to capture os.Exit(1) is discussed here:
// https://talks.golang.org/2014/testing.slide#23
t.Run("lock twice", func(t *testing.T) {
if os.Getenv("CRASH") == "1" {
err := Lock()
if err != nil {
t.Errorf("unexpected error: %v", err)
}
// should fail
Lock()
}
cmd := exec.Command(os.Args[0], "-test.run=TestLock/lock_twice")
cmd.Env = append(os.Environ(), "CRASH=1")
err := cmd.Run()
err, ok := err.(*exec.ExitError)
if !ok {
t.Error("unexpected error")
}
expected := "exit status 1"
if err.Error() != expected {
t.Errorf("got %q, want %q", err.Error(), expected)
}
})
t.Run("set cron", func(t *testing.T) {
expected := int64(5)
SetCron("foo", expected)
result, err := strconv.ParseInt(getLock().GetString("cron.foo"), 10, 64)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if result != expected {
t.Errorf("got %d, want %d", result, expected)
}
})
t.Run("get cron", func(t *testing.T) {
expected := int64(5)
result := GetCron("foo")
if result != expected {
t.Errorf("got %d, want %d", result, expected)
}
})
}

View File

@@ -0,0 +1,22 @@
package metadata
import (
"regexp"
"strings"
)
type addedExtractor struct {
re *regexp.Regexp
}
func (e addedExtractor) Matches(line string) bool {
return e.re.MatchString(line)
}
func (e addedExtractor) Extract(metadata *BackupLogMetadata, line string) {
// Sample line: "Added to the repo: 0 B"
metadata.AddedSize = strings.TrimSpace(e.re.ReplaceAllString(line, "$2"))
}
func NewAddedExtractor() MetadatExtractor {
return addedExtractor{regexp.MustCompile(`(?i)^Added to the repo(sitory)?: ([\d\.]+ \w+)( \([\d\.]+[\w\s]+\))?`)}
}

View File

@@ -0,0 +1,57 @@
package metadata
import (
"regexp"
"strings"
)
type ChangeSetExtractor struct {
re *regexp.Regexp
cleaner *regexp.Regexp
saver changeSetSaver
}
func (e ChangeSetExtractor) Matches(line string) bool {
return e.re.MatchString(line)
}
func (e ChangeSetExtractor) Extract(metadata *BackupLogMetadata, line string) {
// Sample line: "Files: 0 new, 0 changed, 2 unmodified"
trimmed := strings.TrimSpace(e.re.ReplaceAllString(line, ""))
splitted := strings.Split(trimmed, ",")
var changeset BackupLogMetadataChangeset = BackupLogMetadataChangeset{}
changeset.Added = e.cleaner.ReplaceAllString(splitted[0], "")
changeset.Changed = e.cleaner.ReplaceAllString(splitted[1], "")
changeset.Unmodified = e.cleaner.ReplaceAllString(splitted[2], "")
e.saver.Save(metadata, changeset)
}
type changeSetSaver interface {
Save(metadata *BackupLogMetadata, changeset BackupLogMetadataChangeset)
}
type fileSaver struct{}
func (f fileSaver) Save(metadata *BackupLogMetadata, changeset BackupLogMetadataChangeset) {
metadata.Files = changeset
}
type dirsSaver struct{}
func (d dirsSaver) Save(metadata *BackupLogMetadata, changeset BackupLogMetadataChangeset) {
metadata.Dirs = changeset
}
func NewFilesExtractor() MetadatExtractor {
return ChangeSetExtractor{
re: regexp.MustCompile(`(?i)^Files:`),
cleaner: regexp.MustCompile(`[^\d]`),
saver: fileSaver{},
}
}
func NewDirsExtractor() MetadatExtractor {
return ChangeSetExtractor{
re: regexp.MustCompile(`(?i)^Dirs:`),
cleaner: regexp.MustCompile(`[^\d]`),
saver: dirsSaver{},
}
}

View File

@@ -0,0 +1,22 @@
package metadata
import (
"regexp"
"strings"
)
type parentSnapshotIDExtractor struct {
re *regexp.Regexp
}
func (e parentSnapshotIDExtractor) Matches(line string) bool {
return e.re.MatchString(line)
}
func (e parentSnapshotIDExtractor) Extract(metadata *BackupLogMetadata, line string) {
// Sample line: "using parent snapshot c65d9310"
metadata.ParentSnapshotID = strings.TrimSpace(e.re.ReplaceAllString(line, ""))
}
func NewParentSnapshotIDExtractor() MetadatExtractor {
return parentSnapshotIDExtractor{regexp.MustCompile(`(?i)^using parent snapshot`)}
}

View File

@@ -0,0 +1,32 @@
package metadata
import (
"regexp"
"strings"
)
type processedExtractor struct {
re *regexp.Regexp
cleaner *regexp.Regexp
}
func (e processedExtractor) Matches(line string) bool {
return e.re.MatchString(line)
}
func (e processedExtractor) Extract(metadata *BackupLogMetadata, line string) {
// Sample line: "processed 2 files, 24 B in 0:00"
var processed = BackupLogMetadataProcessed{}
split := strings.Split(line, "in")
processed.Duration = strings.TrimSpace(split[1])
split = strings.Split(split[0], ",")
processed.Files = e.cleaner.ReplaceAllString(split[0], "")
processed.Size = strings.TrimSpace(split[1])
metadata.Processed = processed
}
func NewProcessedExtractor() MetadatExtractor {
return processedExtractor{
regexp.MustCompile(`(?i)^processed \d* files`),
regexp.MustCompile(`(?i)[^\d]`),
}
}

View File

@@ -0,0 +1,22 @@
package metadata
import (
"regexp"
"strings"
)
type snapshotExtractor struct {
re *regexp.Regexp
}
func (e snapshotExtractor) Matches(line string) bool {
return e.re.MatchString(line)
}
func (e snapshotExtractor) Extract(metadata *BackupLogMetadata, line string) {
// Sample line: "snapshot 917c7691 saved"
metadata.SnapshotID = strings.Split(line, " ")[1]
}
func NewSnapshotExtractor() MetadatExtractor {
return snapshotExtractor{regexp.MustCompile(`(?i)^snapshot \w+ saved`)}
}

View File

@@ -0,0 +1,74 @@
package metadata
import (
"strings"
)
type BackupLogMetadataChangeset struct {
Added string
Changed string
Unmodified string
}
type BackupLogMetadataProcessed struct {
Files string
Size string
Duration string
}
type BackupLogMetadata struct {
ParentSnapshotID string
Files BackupLogMetadataChangeset
Dirs BackupLogMetadataChangeset
AddedSize string
Processed BackupLogMetadataProcessed
SnapshotID string
ExitCode string
}
type MetadatExtractor interface {
Matches(line string) bool
Extract(metadata *BackupLogMetadata, line string)
}
var extractors = []MetadatExtractor{
NewParentSnapshotIDExtractor(),
NewFilesExtractor(),
NewDirsExtractor(),
NewAddedExtractor(),
NewProcessedExtractor(),
NewSnapshotExtractor(),
}
func ExtractMetadataFromBackupLog(log string) BackupLogMetadata {
var md BackupLogMetadata
for _, line := range strings.Split(log, "\n") {
line = strings.TrimSpace(line)
for _, extractor := range extractors {
if extractor.Matches(line) {
extractor.Extract(&md, line)
continue
}
}
}
return md
}
func MakeEnvFromMetadata(metadata *BackupLogMetadata) map[string]string {
env := make(map[string]string)
var prefix = "AUTORESTIC_"
env[prefix+"SNAPSHOT_ID"] = metadata.SnapshotID
env[prefix+"PARENT_SNAPSHOT_ID"] = metadata.ParentSnapshotID
env[prefix+"FILES_ADDED"] = metadata.Files.Added
env[prefix+"FILES_CHANGED"] = metadata.Files.Changed
env[prefix+"FILES_UNMODIFIED"] = metadata.Files.Unmodified
env[prefix+"DIRS_ADDED"] = metadata.Dirs.Added
env[prefix+"DIRS_CHANGED"] = metadata.Dirs.Changed
env[prefix+"DIRS_UNMODIFIED"] = metadata.Dirs.Unmodified
env[prefix+"ADDED_SIZE"] = metadata.AddedSize
env[prefix+"PROCESSED_FILES"] = metadata.Processed.Files
env[prefix+"PROCESSED_SIZE"] = metadata.Processed.Size
env[prefix+"PROCESSED_DURATION"] = metadata.Processed.Duration
env[prefix+"EXIT_CODE"] = metadata.ExitCode
return env
}

View File

@@ -8,6 +8,8 @@ import (
"os/exec"
"github.com/cupcakearmy/autorestic/internal/colors"
"github.com/cupcakearmy/autorestic/internal/flags"
"github.com/fatih/color"
)
func CheckIfCommandIsCallable(cmd string) bool {
@@ -16,16 +18,28 @@ func CheckIfCommandIsCallable(cmd string) bool {
}
func CheckIfResticIsCallable() bool {
return CheckIfCommandIsCallable("restic")
return CheckIfCommandIsCallable(flags.RESTIC_BIN)
}
type ExecuteOptions struct {
Command string
Envs map[string]string
Dir string
Silent bool
}
func ExecuteCommand(options ExecuteOptions, args ...string) (string, error) {
type ColoredWriter struct {
target io.Writer
color *color.Color
}
func (w ColoredWriter) Write(p []byte) (n int, err error) {
colored := []byte(w.color.Sprint(string(p)))
w.target.Write(colored)
return len(p), nil
}
func ExecuteCommand(options ExecuteOptions, args ...string) (int, string, error) {
cmd := exec.Command(options.Command, args...)
env := os.Environ()
for k, v := range options.Envs {
@@ -34,34 +48,50 @@ func ExecuteCommand(options ExecuteOptions, args ...string) (string, error) {
cmd.Env = env
cmd.Dir = options.Dir
if VERBOSE {
if flags.VERBOSE {
colors.Faint.Printf("> Executing: %s\n", cmd)
}
var out bytes.Buffer
var error bytes.Buffer
cmd.Stdout = &out
if flags.VERBOSE && !options.Silent {
var colored ColoredWriter = ColoredWriter{
target: os.Stdout,
color: colors.Faint,
}
mw := io.MultiWriter(colored, &out)
cmd.Stdout = mw
} else {
cmd.Stdout = &out
}
cmd.Stderr = &error
err := cmd.Run()
if err != nil {
return error.String(), err
code := -1
if exitError, ok := err.(*exec.ExitError); ok {
code = exitError.ExitCode()
}
return code, error.String(), err
}
return out.String(), nil
return 0, out.String(), nil
}
func ExecuteResticCommand(options ExecuteOptions, args ...string) (string, error) {
options.Command = "restic"
func ExecuteResticCommand(options ExecuteOptions, args ...string) (int, string, error) {
options.Command = flags.RESTIC_BIN
var c = GetConfig()
var optionsAsString = getOptions(c.Global, []string{"all"})
args = append(optionsAsString, args...)
return ExecuteCommand(options, args...)
}
func CopyFile(from, to string) error {
original, err := os.Open("original.txt")
original, err := os.Open(from)
if err != nil {
return nil
}
defer original.Close()
new, err := os.Create("new.txt")
new, err := os.Create(to)
if err != nil {
return nil
}
@@ -72,3 +102,17 @@ func CopyFile(from, to string) error {
}
return nil
}
func CheckIfVolumeExists(volume string) bool {
_, _, err := ExecuteCommand(ExecuteOptions{Command: "docker"}, "volume", "inspect", volume)
return err == nil
}
func ArrayContains[T comparable](arr []T, needle T) bool {
for _, item := range arr {
if item == needle {
return true
}
}
return false
}