mirror of
https://github.com/cupcakearmy/autorestic.git
synced 2025-04-03 16:07:08 +00:00
Compare commits
163 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
bb29a98614 | ||
|
39f4f87ce3 | ||
|
bd36bbe429 | ||
|
48fa20b482 | ||
|
f7d28b486c | ||
|
6895df1c83 | ||
|
8a773856de | ||
|
41e4e4a5f3 | ||
|
6424c64304 | ||
|
d39dafaef1 | ||
|
5a0f7e94f4 | ||
|
6a60d02759 | ||
1f7240c6a0 | |||
592d1093ac | |||
83481072bb | |||
|
776638e6fe | ||
|
bc74d3f13e | ||
|
dd01e97cf3 | ||
|
aabb0989c6 | ||
62a81d1420 | |||
e4b33cad1f | |||
|
a82273ec13 | ||
|
2418da5636 | ||
|
7508df7d66 | ||
6e34196220 | |||
|
dc56911a45 | ||
edb3ba35d8 | |||
|
12f6143bb4 | ||
|
a6bf1d1408 | ||
|
13aa560fda | ||
|
bbb1c85cad | ||
|
4cc44315ab | ||
|
b3440cd87c | ||
|
4848702929 | ||
b5604b8b9f | |||
|
24220f6b62 | ||
ced20801c1 | |||
|
ce9140fa1e | ||
|
046c79fd15 | ||
|
f8603425d1 | ||
068121d722 | |||
8eea7d33f8 | |||
|
fc8b5fdbe2 | ||
|
f67bb7f73c | ||
|
530b1b646c | ||
|
3b57602fe8 | ||
|
045513234f | ||
|
78b0db50e0 | ||
|
62dd371d51 | ||
|
08766b75db | ||
b3b7c8df95 | |||
2c5266c9a0 | |||
087b293c39 | |||
|
112a69d743 | ||
|
37d55c691f | ||
|
715b6f791c | ||
|
38b38c6805 | ||
c82db56069 | |||
27821dc3ef | |||
866975d32d | |||
|
955ac0e323 | ||
|
1512db5b55 | ||
|
046331748c | ||
72a40eaaa2 | |||
ec61effe22 | |||
d6acab94a5 | |||
|
d0d2fcf0f2 | ||
58fd41fafb | |||
|
d0c4a32879 | ||
74979e9a2a | |||
|
3732dcf6ff | ||
|
874ed52e3b | ||
4d9a2b828e | |||
|
b830667264 | ||
|
83eeb847ac | ||
|
a89ba5a40a | ||
|
6990bf6adc | ||
|
2f407cf211 | ||
489f3078fe | |||
|
e07dd0d991 | ||
|
2b9dc9f17c | ||
|
465bc037c2 | ||
|
37a043afff | ||
|
e91b632181 | ||
|
2b30998b9a | ||
|
49b37a0a9a | ||
3bc091f826 | |||
5bcf5c9217 | |||
ff2e3714d1 | |||
2e6764223d | |||
434862ed4e | |||
2da440a1cd | |||
0f6c34dc6b | |||
3457fc01bf | |||
|
1e62f6cb05 | ||
8112aceb30 | |||
|
b4cc0f0ab1 | ||
|
a3e539b0b7 | ||
|
79164e3de0 | ||
8b74a98836 | |||
8a713e497d | |||
71601ae7a6 | |||
|
85d3f06b79 | ||
5afad86e37 | |||
ba9e090695 | |||
|
05def04770 | ||
d85470459f | |||
1f69a7974a | |||
db9f5dea66 | |||
75160d4d6a | |||
92feaef5bb | |||
2a7e233cdb | |||
a373c07fb0 | |||
ec9e2aebcd | |||
7d87160706 | |||
8e1fe6af65 | |||
|
65ba1f6ac1 | ||
|
6bf4953003 | ||
27758a03fa | |||
bbdae05199 | |||
389490c4ea | |||
21b83b4c89 | |||
982f9e0b5c | |||
0c37af5588 | |||
e3c378f2a1 | |||
3b541665ae | |||
e0b2c99ccd | |||
2b14e6b1af | |||
1810af8d02 | |||
252968e15e | |||
26de4385ea | |||
0c71bea93e | |||
3029259d82 | |||
|
389f7c25dd | ||
e055e28cfe | |||
ed3c17d678 | |||
8802b74b47 | |||
e94e7030fc | |||
c55e91b8ff | |||
170bdb81ad | |||
4126436f7f | |||
113a97c283 | |||
c250391f67 | |||
d3b4915d25 | |||
cd7a5cbc13 | |||
3dd3956d64 | |||
59035da46a | |||
90914d2078 | |||
0ae374cd45 | |||
3665cea62d | |||
cf92d400c2 | |||
696bd14ac7 | |||
a68e3e426e | |||
27e82c8529 | |||
4fe241e6f3 | |||
d0b1b86fdd | |||
14dd41d60d | |||
bcfc734cd1 | |||
6817f494ef | |||
2c46f0da0c | |||
ac756dfbde | |||
7ad6f7ce9e | |||
c2f9ed9204 |
6
.dockerignore
Normal file
6
.dockerignore
Normal file
@ -0,0 +1,6 @@
|
||||
*
|
||||
!**/*.go
|
||||
!build
|
||||
!cmd
|
||||
!internal
|
||||
!go.*
|
8
.github/dependabot.yml
vendored
Normal file
8
.github/dependabot.yml
vendored
Normal 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"
|
43
.github/workflows/build.yml
vendored
43
.github/workflows/build.yml
vendored
@ -6,23 +6,46 @@ on:
|
||||
- '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.21'
|
||||
- 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 }}
|
||||
|
24
.github/workflows/ci.yml
vendored
Normal file
24
.github/workflows/ci.yml
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
name: CI
|
||||
on:
|
||||
pull_request:
|
||||
push:
|
||||
branches: [master]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '^1.21'
|
||||
- run: go test -v ./...
|
||||
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/setup-go@v3
|
||||
with:
|
||||
go-version: '^1.21'
|
||||
- run: go build -v .
|
181
CHANGELOG.md
181
CHANGELOG.md
@ -5,9 +5,146 @@ 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
|
||||
|
||||
### Fixes
|
||||
### Fixed
|
||||
|
||||
- Numeric values from config files not being passed to env.
|
||||
|
||||
@ -15,26 +152,26 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
### Added
|
||||
|
||||
- Allow specify to specify a backend for location backup
|
||||
- Global restic flags
|
||||
- Generic ENV support for backends
|
||||
- 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`
|
||||
- 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
|
||||
- Error handling during upgrade & uninstall.
|
||||
|
||||
## [1.3.0] - 2021-10-26
|
||||
|
||||
### Added
|
||||
|
||||
- Pass restic backup metadata as ENV to hooks
|
||||
- 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
|
||||
- Binary restic flags are now supported.
|
||||
- Pass encryption keys from env variables or files.
|
||||
|
||||
## [1.2.0] - 2021-08-05
|
||||
@ -42,12 +179,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
### Added
|
||||
|
||||
- Community page
|
||||
- Support for yaml references and aliases
|
||||
- Support for yaml references and aliases.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Better verbose output for hooks
|
||||
- Better error message for bad formatted configs
|
||||
- Better verbose output for hooks.
|
||||
- Better error message for bad formatted configs.
|
||||
|
||||
## [1.1.2] - 2021-07-11
|
||||
|
||||
@ -59,24 +196,24 @@ Don't check all backend when running `forget` or `exec` commands.
|
||||
|
||||
### Added
|
||||
|
||||
- Options for backends
|
||||
- Options for backends.
|
||||
|
||||
## [1.1.0] - 2021-05-06
|
||||
|
||||
### Added
|
||||
|
||||
- use custom restic binary
|
||||
- success & failure hooks
|
||||
- use custom restic binary.
|
||||
- success & failure hooks.
|
||||
|
||||
### Fixed
|
||||
|
||||
- don't skip other locations on failure
|
||||
- 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
|
||||
|
||||
@ -99,7 +236,7 @@ Don't check all backend when running `forget` or `exec` commands.
|
||||
|
||||
### Added
|
||||
|
||||
- Support for rclone
|
||||
- Support for rclone.
|
||||
|
||||
## [1.0.5] - 2021-04-24
|
||||
|
||||
@ -112,17 +249,17 @@ Don't check all backend when running `forget` or `exec` commands.
|
||||
|
||||
### 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
|
||||
|
||||
@ -138,7 +275,7 @@ Don't check all backend when running `forget` or `exec` commands.
|
||||
|
||||
### Added
|
||||
|
||||
- Completion command for various shells
|
||||
- Completion command for various shells.
|
||||
|
||||
## [1.0.0] - 2021-04-17
|
||||
|
||||
|
13
Dockerfile
Normal file
13
Dockerfile
Normal file
@ -0,0 +1,13 @@
|
||||
FROM golang:1.24-alpine as builder
|
||||
|
||||
WORKDIR /app
|
||||
COPY go.* .
|
||||
RUN go mod download
|
||||
COPY . .
|
||||
RUN go build
|
||||
|
||||
FROM restic/restic:0.17.3
|
||||
RUN apk add --no-cache rclone bash curl docker-cli
|
||||
COPY --from=builder /app/autorestic /usr/bin/autorestic
|
||||
ENTRYPOINT []
|
||||
CMD [ "autorestic" ]
|
@ -34,7 +34,7 @@ Autorestic is a wrapper around the amazing [restic](https://restic.net/). While
|
||||
- Backup locations to multiple backends
|
||||
- Snapshot policies and pruning
|
||||
- Fully encrypted
|
||||
- Pre/After hooks
|
||||
- Before/after backup hooks
|
||||
- Exclude pattern/files
|
||||
- Cron jobs for automatic backup
|
||||
- Backup & Restore docker volume
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -18,9 +18,11 @@ var backupCmd = &cobra.Command{
|
||||
err := lock.Lock()
|
||||
CheckErr(err)
|
||||
defer lock.Unlock()
|
||||
dry, _ := cmd.Flags().GetBool("dry-run")
|
||||
|
||||
selected, err := internal.GetAllOrSelected(cmd, false)
|
||||
CheckErr(err)
|
||||
|
||||
errors := 0
|
||||
for _, name := range selected {
|
||||
var splitted = strings.Split(name, "@")
|
||||
@ -29,9 +31,9 @@ var backupCmd = &cobra.Command{
|
||||
specificBackend = splitted[1]
|
||||
}
|
||||
location, _ := internal.GetLocation(splitted[0])
|
||||
errs := location.Backup(false, specificBackend)
|
||||
for err := range errs {
|
||||
colors.Error.Println(err)
|
||||
errs := location.Backup(false, dry, specificBackend)
|
||||
for _, err := range errs {
|
||||
colors.Error.Printf("%s\n\n", err)
|
||||
errors++
|
||||
}
|
||||
}
|
||||
@ -44,4 +46,5 @@ var backupCmd = &cobra.Command{
|
||||
func init() {
|
||||
rootCmd.AddCommand(backupCmd)
|
||||
internal.AddFlagsToCommand(backupCmd, false)
|
||||
backupCmd.Flags().Bool("dry-run", false, "do not write changes, show what would be affected")
|
||||
}
|
||||
|
@ -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()
|
||||
|
@ -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")
|
||||
}
|
||||
|
17
cmd/exec.go
17
cmd/exec.go
@ -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"
|
||||
@ -18,10 +20,23 @@ var execCmd = &cobra.Command{
|
||||
|
||||
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)))
|
||||
}
|
||||
},
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
|
63
cmd/root.go
63
cmd/root.go
@ -3,9 +3,11 @@ 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"
|
||||
|
||||
@ -27,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() {
|
||||
@ -36,40 +38,55 @@ 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().StringVar(&internal.RESTIC_BIN, "restic-bin", "restic", "specify custom restic binary")
|
||||
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 {
|
||||
viper.AddConfigPath(".")
|
||||
|
||||
// Home
|
||||
if home, err := homedir.Dir(); err != nil {
|
||||
viper.AddConfigPath(home)
|
||||
configPaths := getConfigPaths()
|
||||
for _, cfgPath := range configPaths {
|
||||
viper.AddConfigPath(cfgPath)
|
||||
}
|
||||
|
||||
// XDG_CONFIG_HOME
|
||||
{
|
||||
prefix, found := os.LookupEnv("XDG_CONFIG_HOME")
|
||||
if !found {
|
||||
if home, err := homedir.Dir(); err != nil {
|
||||
prefix = filepath.Join(home, ".config")
|
||||
}
|
||||
}
|
||||
viper.AddConfigPath(filepath.Join(prefix, "autorestic"))
|
||||
if flags.VERBOSE {
|
||||
colors.Faint.Printf("Using config paths: %s\n", strings.Join(configPaths, " "))
|
||||
}
|
||||
|
||||
viper.SetConfigName(".autorestic")
|
||||
cfgFileName := ".autorestic"
|
||||
viper.SetConfigName(cfgFileName)
|
||||
viper.AutomaticEnv()
|
||||
}
|
||||
viper.AutomaticEnv()
|
||||
}
|
||||
|
||||
func getConfigPaths() []string {
|
||||
result := []string{"."}
|
||||
if home, err := homedir.Dir(); err == nil {
|
||||
result = append(result, home)
|
||||
}
|
||||
|
||||
{
|
||||
xdgConfigHome, found := os.LookupEnv("XDG_CONFIG_HOME")
|
||||
if !found {
|
||||
if home, err := homedir.Dir(); err == nil {
|
||||
xdgConfigHome = filepath.Join(home, ".config")
|
||||
}
|
||||
}
|
||||
xdgConfig := filepath.Join(xdgConfigHome, "autorestic")
|
||||
result = append(result, xdgConfig)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
36
cmd/root_test.go
Normal file
36
cmd/root_test.go
Normal file
@ -0,0 +1,36 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/mitchellh/go-homedir"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const xdgConfigHome = "XDG_CONFIG_HOME"
|
||||
|
||||
func assertContains(t *testing.T, array []string, element string) {
|
||||
if !slices.Contains(array, element) {
|
||||
t.Errorf("Expected %s to be contained in %s", element, array)
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigResolving(t *testing.T) {
|
||||
t.Run("~/.config/autorestic is used if XDG_CONFIG_HOME is not set", func(t *testing.T) {
|
||||
// Override env using testing so that env gets restored after test
|
||||
t.Setenv(xdgConfigHome, "")
|
||||
_ = os.Unsetenv("XDG_CONFIG_HOME")
|
||||
configPaths := getConfigPaths()
|
||||
homeDir, _ := homedir.Dir()
|
||||
expectedConfigPath := filepath.Join(homeDir, ".config/autorestic")
|
||||
assertContains(t, configPaths, expectedConfigPath)
|
||||
})
|
||||
|
||||
t.Run("XDG_CONFIG_HOME is respected if set", func(t *testing.T) {
|
||||
t.Setenv(xdgConfigHome, "/foo/bar")
|
||||
|
||||
configPaths := getConfigPaths()
|
||||
assertContains(t, configPaths, filepath.Join("/", "foo", "bar", "autorestic"))
|
||||
})
|
||||
}
|
81
cmd/unlock.go
Normal file
81
cmd/unlock.go
Normal file
@ -0,0 +1,81 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/cupcakearmy/autorestic/internal"
|
||||
"github.com/cupcakearmy/autorestic/internal/colors"
|
||||
"github.com/cupcakearmy/autorestic/internal/lock"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var unlockCmd = &cobra.Command{
|
||||
Use: "unlock",
|
||||
Short: "Unlock autorestic only if you are sure that no other instance is running",
|
||||
Long: `Unlock autorestic only if you are sure that no other instance is running.
|
||||
To check you can run "ps aux | grep autorestic".`,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
internal.GetConfig()
|
||||
|
||||
force, _ := cmd.Flags().GetBool("force")
|
||||
|
||||
if !force && isAutoresticRunning() {
|
||||
colors.Error.Print("Another autorestic instance is running. Are you sure you want to unlock? (yes/no): ")
|
||||
var response string
|
||||
fmt.Scanln(&response)
|
||||
if strings.ToLower(response) != "yes" {
|
||||
colors.Primary.Println("Unlocking aborted.")
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
err := lock.Unlock()
|
||||
if err != nil {
|
||||
colors.Error.Println("Could not unlock:", err)
|
||||
return
|
||||
}
|
||||
|
||||
colors.Success.Println("Unlock successful")
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(unlockCmd)
|
||||
unlockCmd.Flags().Bool("force", false, "force unlock")
|
||||
}
|
||||
|
||||
// isAutoresticRunning checks if autorestic is running
|
||||
// and returns true if it is.
|
||||
// It also prints the processes to stdout.
|
||||
func isAutoresticRunning() bool {
|
||||
cmd := exec.Command("sh", "-c", "ps aux | grep autorestic")
|
||||
var out bytes.Buffer
|
||||
cmd.Stdout = &out
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
lines := strings.Split(out.String(), "\n")
|
||||
autoresticProcesses := []string{}
|
||||
currentPid := fmt.Sprint(os.Getpid())
|
||||
|
||||
for _, line := range lines {
|
||||
if strings.Contains(line, "autorestic") && !strings.Contains(line, "grep autorestic") && !strings.Contains(line, currentPid) {
|
||||
autoresticProcesses = append(autoresticProcesses, line)
|
||||
}
|
||||
}
|
||||
|
||||
if len(autoresticProcesses) > 0 {
|
||||
colors.Faint.Println("Found autorestic processes:")
|
||||
for _, proc := range autoresticProcesses {
|
||||
colors.Faint.Println(proc)
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
import { build } from '@codedoc/core';
|
||||
|
||||
import { config } from './config';
|
||||
import { installTheme$ } from './content/theme';
|
||||
import { content } from './content';
|
||||
|
||||
|
||||
build(config, content, installTheme$, {
|
||||
resolve: {
|
||||
modules: ['.codedoc/node_modules']
|
||||
},
|
||||
resolveLoader: {
|
||||
modules: ['.codedoc/node_modules']
|
||||
}
|
||||
});
|
@ -1,24 +0,0 @@
|
||||
import { configuration } from '@codedoc/core'
|
||||
|
||||
export const config = configuration({
|
||||
src: {
|
||||
base: 'markdown',
|
||||
},
|
||||
dest: {
|
||||
html: './build',
|
||||
assets: './build',
|
||||
bundle: './_',
|
||||
styles: './_',
|
||||
},
|
||||
page: {
|
||||
title: {
|
||||
base: 'Autorestic',
|
||||
},
|
||||
},
|
||||
misc: {
|
||||
github: {
|
||||
user: 'cupcakearmy',
|
||||
repo: 'autorestic',
|
||||
},
|
||||
},
|
||||
})
|
@ -1,19 +0,0 @@
|
||||
import { CodedocConfig } from '@codedoc/core';
|
||||
import { Footer as _Footer, GitterToggle$, Watermark} from '@codedoc/core/components';
|
||||
|
||||
|
||||
export function Footer(config: CodedocConfig, renderer: any) {
|
||||
let github$;
|
||||
if (config.misc?.github)
|
||||
github$ = <a href={`https://github.com/${config.misc.github.user}/${config.misc.github.repo}/`}
|
||||
target="_blank">GitHub</a>;
|
||||
|
||||
let community$;
|
||||
if (config.misc?.gitter)
|
||||
community$ = <GitterToggle$ room={config.misc.gitter.room}/>
|
||||
|
||||
if (github$ && community$) return <_Footer>{github$}<hr/>{community$}</_Footer>;
|
||||
else if (github$) return <_Footer>{github$}</_Footer>;
|
||||
else if (community$) return <_Footer>{community$}</_Footer>;
|
||||
else return <_Footer><Watermark/></_Footer>;
|
||||
}
|
@ -1,21 +0,0 @@
|
||||
import { CodedocConfig } from '@codedoc/core';
|
||||
import { Header as _Header, GithubButton, Watermark } from '@codedoc/core/components';
|
||||
|
||||
|
||||
export function Header(config: CodedocConfig, renderer: any) {
|
||||
return (
|
||||
<_Header>{config.misc?.github ?
|
||||
<fragment>
|
||||
<GithubButton action={config.misc.github.action || 'Star'}
|
||||
repo={config.misc.github.repo}
|
||||
user={config.misc.github.user}
|
||||
large={config.misc.github.large === true}
|
||||
count={config.misc.github.count !== false}
|
||||
standardIcon={config.misc.github.standardIcon !== false}/>
|
||||
<br/><br/>
|
||||
</fragment>
|
||||
: ''}
|
||||
<Watermark/>
|
||||
</_Header>
|
||||
)
|
||||
}
|
@ -1,57 +0,0 @@
|
||||
import { RendererLike } from '@connectv/html'
|
||||
import { File } from 'rxline/fs'
|
||||
import {
|
||||
Page,
|
||||
Meta,
|
||||
ContentNav,
|
||||
Fonts,
|
||||
ToC,
|
||||
GithubSearch$,
|
||||
} from '@codedoc/core/components'
|
||||
|
||||
import { config } from '../config'
|
||||
import { Header } from './header'
|
||||
import { Footer } from './footer'
|
||||
|
||||
export function content(
|
||||
_content: HTMLElement,
|
||||
toc: HTMLElement,
|
||||
renderer: RendererLike<any, any>,
|
||||
file: File<string>
|
||||
) {
|
||||
return (
|
||||
<Page
|
||||
title={config.page.title.extractor(_content, config, file)}
|
||||
favicon={config.page.favicon}
|
||||
meta={<Meta {...config.page.meta} />}
|
||||
fonts={<Fonts {...config.page.fonts} />}
|
||||
scripts={config.page.scripts}
|
||||
stylesheets={config.page.stylesheets}
|
||||
header={<Header {...config} />}
|
||||
footer={<Footer {...config} />}
|
||||
toc={
|
||||
<ToC
|
||||
default={'open'}
|
||||
search={
|
||||
config.misc?.github ? (
|
||||
<GithubSearch$
|
||||
repo={config.misc.github.repo}
|
||||
user={config.misc.github.user}
|
||||
root={config.src.base}
|
||||
pick={config.src.pick.source}
|
||||
drop={config.src.drop.source}
|
||||
/>
|
||||
) : (
|
||||
false
|
||||
)
|
||||
}
|
||||
>
|
||||
{toc}
|
||||
</ToC>
|
||||
}
|
||||
>
|
||||
{_content}
|
||||
<ContentNav content={_content} />
|
||||
</Page>
|
||||
)
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
import { funcTransport } from '@connectv/sdh/transport';
|
||||
import { useTheme } from '@codedoc/core/transport';
|
||||
|
||||
import { theme } from '../theme';
|
||||
|
||||
|
||||
export function installTheme() { useTheme(theme); }
|
||||
export const installTheme$ = /*#__PURE__*/funcTransport(installTheme);
|
10811
docs/.codedoc/package-lock.json
generated
10811
docs/.codedoc/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,5 +0,0 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@codedoc/core": "^0.2.23"
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
import { join } from 'path';
|
||||
import { serve } from '@codedoc/core';
|
||||
|
||||
import { config } from './config';
|
||||
import { content } from './content';
|
||||
import { installTheme$ } from './content/theme';
|
||||
|
||||
|
||||
const root = join(__dirname, '../');
|
||||
|
||||
serve(root, config, content, installTheme$, {
|
||||
resolve: {
|
||||
modules: ['.codedoc/node_modules']
|
||||
},
|
||||
resolveLoader: {
|
||||
modules: ['.codedoc/node_modules']
|
||||
}
|
||||
});
|
@ -1,11 +0,0 @@
|
||||
import { createTheme } from '@codedoc/core/transport';
|
||||
|
||||
|
||||
export const theme = /*#__PURE__*/createTheme({
|
||||
light: {
|
||||
primary: '#1eb2a6'
|
||||
},
|
||||
dark: {
|
||||
primary: '#1eb2a6'
|
||||
}
|
||||
});
|
@ -1,26 +0,0 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "es6",
|
||||
"noImplicitAny": true,
|
||||
"declaration": false,
|
||||
"strictNullChecks": true,
|
||||
"strictFunctionTypes": true,
|
||||
"noImplicitThis": true,
|
||||
"alwaysStrict": true,
|
||||
"sourceMap": true,
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"jsx": "react",
|
||||
"jsxFactory": "renderer.create",
|
||||
"lib": [
|
||||
"es2017",
|
||||
"dom"
|
||||
]
|
||||
},
|
||||
"include": [
|
||||
"./**/*"
|
||||
]
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
import { exec, spawn } from 'child_process';
|
||||
import { config } from './config';
|
||||
|
||||
|
||||
const cmd = 'ts-node-dev';
|
||||
const params = `--project .codedoc/tsconfig.json`
|
||||
+ ` -T --watch ${config.src.base},.codedoc`
|
||||
+ ` --ignore-watch .codedoc/node_modules`
|
||||
+ ` .codedoc/serve`;
|
||||
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
const child = exec(cmd + ' ' + params);
|
||||
|
||||
child.stdout?.pipe(process.stdout);
|
||||
child.stderr?.pipe(process.stderr);
|
||||
child.on('close', () => {});
|
||||
}
|
||||
else {
|
||||
const child = spawn(cmd, [params], { stdio: 'inherit', shell: 'bash' });
|
||||
child.on('close', () => {});
|
||||
}
|
2
docs/.gitignore
vendored
2
docs/.gitignore
vendored
@ -1,2 +1,2 @@
|
||||
node_modules
|
||||
build
|
||||
.next
|
||||
|
1
docs/.nvmrc
Normal file
1
docs/.nvmrc
Normal file
@ -0,0 +1 @@
|
||||
v22.7.0
|
@ -1,47 +0,0 @@
|
||||
[Home](/)
|
||||
[Quick Start](/quick)
|
||||
[Installation](/installation)
|
||||
[Configuration](/config)
|
||||
[Upgrade](/upgrade)
|
||||
|
||||
> :Collapse label=Locations
|
||||
>
|
||||
> [Overview](/location/overview)
|
||||
> [Hooks](/location/hooks)
|
||||
>
|
||||
> > :Collapse label=Options
|
||||
> >
|
||||
> > [Overview](/location/options)
|
||||
> > [Excluding Files](/location/exclude)
|
||||
> > [Forget Policy](/location/forget)
|
||||
>
|
||||
> [Cron](/location/cron)
|
||||
> [Docker Volumes](/location/docker)
|
||||
|
||||
> :Collapse label=Backend
|
||||
>
|
||||
> [Overview](/backend/overview)
|
||||
> [Available Backends](/backend/available)
|
||||
> [Options](/backend/options)
|
||||
> [Environment](/backend/env)
|
||||
|
||||
> :Collapse label=CLI
|
||||
>
|
||||
> [General](/cli/general)
|
||||
> [Info](/cli/info)
|
||||
> [Check](/cli/check)
|
||||
> [Completion](/cli/completion)
|
||||
> [Backup](/cli/backup)
|
||||
> [Restore](/cli/restore)
|
||||
> [Forget](/cli/forget)
|
||||
> [Cron](/cli/cron)
|
||||
> [Exec](/cli/exec)
|
||||
> [Install](/cli/install)
|
||||
> [Uninstall](/cli/uninstall)
|
||||
> [Upgrade](/cli/upgrade)
|
||||
|
||||
[Examples](/examples)
|
||||
[QA](/qa)
|
||||
[Community](/community)
|
||||
[Contributors](/contrib)
|
||||
|
@ -1,16 +0,0 @@
|
||||
# 💽 Backends
|
||||
|
||||
Backends are the outputs of the backup process. Each location needs at least one.
|
||||
|
||||
```yaml | .autorestic.yml
|
||||
backends:
|
||||
name-of-backend:
|
||||
type: local
|
||||
path: /data/my/backups
|
||||
```
|
||||
|
||||
## Types
|
||||
|
||||
We restic supports multiple types of backends. See the [full list](/backend/available) for details.
|
||||
|
||||
> :ToCPrevNext
|
@ -1,19 +0,0 @@
|
||||
# Restore
|
||||
|
||||
```bash
|
||||
autorestic restore [-l, --location] [--from backend] [--to <out dir>] [-f, --force]
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
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.
|
||||
|
||||
## Example
|
||||
|
||||
```bash
|
||||
autorestic restore -l home --from hdd --to /path/where/to/restore
|
||||
```
|
||||
|
||||
This will restore the location `home` to the `/path/where/to/restore` folder and taking the data from the backend `hdd`
|
||||
|
||||
> :ToCPrevNext
|
@ -1,11 +0,0 @@
|
||||
# 🏘 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
|
||||
|
||||
> :ToCPrevNext
|
@ -1,17 +0,0 @@
|
||||
# 🙋♀️🙋♂️ Contributors
|
||||
|
||||
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
|
||||
- @somebox - Typos
|
||||
|
||||
> :ToCPrevNext
|
@ -1,17 +0,0 @@
|
||||
# 🐣 Examples
|
||||
|
||||
## List all the snapshots for all the backends
|
||||
|
||||
```bash
|
||||
autorestic exec -av -- snapshots
|
||||
```
|
||||
|
||||
## Unlock a locked repository
|
||||
|
||||
This can come in handy if a backup process crashed or if it was accidentally cancelled. Then the repository would still be locked without an actual process using it. Only do this if you know what you are doing and are sure no other process is actually reading/writing to the repository of course.
|
||||
|
||||
```bash
|
||||
autorestic exec -b my-backend -- unlock
|
||||
```
|
||||
|
||||
> :ToCPrevNext
|
6
docs/next.config.js
Normal file
6
docs/next.config.js
Normal file
@ -0,0 +1,6 @@
|
||||
const withNextra = require('nextra')({
|
||||
theme: 'nextra-theme-docs',
|
||||
themeConfig: './theme.config.jsx',
|
||||
})
|
||||
|
||||
module.exports = withNextra()
|
1122
docs/package-lock.json
generated
1122
docs/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -1,10 +1,15 @@
|
||||
{
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "codedoc install && codedoc build",
|
||||
"dev": "codedoc serve"
|
||||
"build": "NEXT_TELEMETRY_DISABLED=1 next build",
|
||||
"dev": "NEXT_TELEMETRY_DISABLED=1 next",
|
||||
"start": "NEXT_TELEMETRY_DISABLED=1 next start"
|
||||
},
|
||||
"dependencies": {
|
||||
"@codedoc/cli": "^0.2.8"
|
||||
}
|
||||
"next": "^14.2.7",
|
||||
"nextra": "^2.13.4",
|
||||
"nextra-theme-docs": "^2.13.4",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1"
|
||||
},
|
||||
"packageManager": "pnpm@9.9.0"
|
||||
}
|
||||
|
10
docs/pages/_meta.json
Normal file
10
docs/pages/_meta.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"index": "Home",
|
||||
"quick": "Quick Start",
|
||||
"installation": "Installation",
|
||||
"config": "Configuration",
|
||||
"location": "Locations",
|
||||
"backend": "Backend",
|
||||
"cli": "CLI",
|
||||
"migration": "Migration"
|
||||
}
|
6
docs/pages/backend/_meta.json
Normal file
6
docs/pages/backend/_meta.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"index": "Overview",
|
||||
"available": "Available backends",
|
||||
"options": "Options",
|
||||
"env": "Environment"
|
||||
}
|
@ -83,5 +83,3 @@ backends:
|
||||
user: user
|
||||
password: pass
|
||||
```
|
||||
|
||||
> :ToCPrevNext
|
@ -63,5 +63,3 @@ backends:
|
||||
type: b2
|
||||
path: myBucket
|
||||
```
|
||||
|
||||
> :ToCPrevNext
|
40
docs/pages/backend/index.md
Normal file
40
docs/pages/backend/index.md
Normal file
@ -0,0 +1,40 @@
|
||||
# 💽 Backends
|
||||
|
||||
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
|
||||
path: /data/my/backups
|
||||
```
|
||||
|
||||
## Types
|
||||
|
||||
We restic supports multiple types of backends. See the [full list](/backend/available) for details.
|
||||
|
||||
## Avoid Generating Keys
|
||||
|
||||
By default, `autorestic` will generate a key for every backend if none is defined. This is done by updating your config file with the key.
|
||||
|
||||
In cases where you want to provide the key yourself, you can ensure that `autorestic` doesn't accidentally generate one for you by setting `requireKey: true`.
|
||||
|
||||
Example:
|
||||
|
||||
```yaml | .autorestic.yml
|
||||
version: 2
|
||||
|
||||
backends:
|
||||
foo:
|
||||
type: local
|
||||
path: /data/my/backups
|
||||
# Alternatively, you can set the key through the `AUTORESTIC_FOO_RESTIC_PASSWORD` environment variable.
|
||||
key: ... your key here ...
|
||||
requireKey: true
|
||||
```
|
||||
|
||||
With this setting, if a key is missing, `autorestic` will crash instead of generating a new key and updating your config file.
|
@ -15,5 +15,3 @@ backend:
|
||||
```
|
||||
|
||||
In this example, whenever `autorestic` runs `restic backup` it will append a `--tag abc --tag` to the native command.
|
||||
|
||||
> :ToCPrevNext
|
3
docs/pages/cli/_meta.json
Normal file
3
docs/pages/cli/_meta.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"general": "General"
|
||||
}
|
@ -1,11 +1,14 @@
|
||||
# Backup
|
||||
|
||||
```bash
|
||||
autorestic backup [-l, --location] [-a, --all]
|
||||
autorestic backup [-l, --location] [-a, --all] [--dry-run]
|
||||
```
|
||||
|
||||
Performs a backup of all locations if the `-a` flag is passed. To only backup some locations pass one or more `-l` or `--location` flags.
|
||||
|
||||
The `--dry-run` flag will do a dry run showing what would have been deleted, but won't touch the actual data.
|
||||
|
||||
|
||||
```bash
|
||||
# All
|
||||
autorestic backup -a
|
||||
@ -21,5 +24,3 @@ autorestic backup -l foo -l bar
|
||||
```bash
|
||||
autorestic backup -l location@backend
|
||||
```
|
||||
|
||||
> :ToCPrevNext
|
@ -7,5 +7,3 @@ autorestic check
|
||||
Checks locations and backends are configured properly and initializes them if they are not already.
|
||||
|
||||
This is mostly an internal command, but useful to verify if a backend is configured correctly.
|
||||
|
||||
> :ToCPrevNext
|
@ -13,5 +13,3 @@ Supported shells are
|
||||
- powershell
|
||||
|
||||
To see how to install run `autorestic help completion` and follow the instructions for your specific shell
|
||||
|
||||
> :ToCPrevNext
|
@ -9,5 +9,3 @@ This command is mostly intended to be triggered by an automated system like syst
|
||||
It will run cron jobs as [specified in the cron section](/location/cron) of a specific location.
|
||||
|
||||
The `--lean` flag will omit output like _skipping location x: not due yet_. This can be useful if you are dumping the output of the cron job to a log file and don't want to be overwhelmed by the output log.
|
||||
|
||||
> :ToCPrevNext
|
@ -11,5 +11,3 @@ autorestic exec -av -- snapshots
|
||||
```
|
||||
|
||||
With `exec` you can basically run every cli command that you would be able to run with the restic cli. It only pre-fills path, key, etc.
|
||||
|
||||
> :ToCPrevNext
|
@ -4,10 +4,8 @@
|
||||
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/options/forget).
|
||||
|
||||
The `--dry-run` flag will do a dry run showing what would have been deleted, but won't touch the actual data.
|
||||
|
||||
The `--prune` flag will also [prune the data](https://restic.readthedocs.io/en/latest/060_forget.html#removing-backup-snapshots). This is a costly operation that can take longer, however it will free up the actual space.
|
||||
|
||||
> :ToCPrevNext
|
@ -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
|
||||
@ -34,5 +34,3 @@ With `--restic-bin` you can specify to run a specific restic binary. This can be
|
||||
```bash
|
||||
autorestic --restic-bin /some/path/to/my/custom/restic/binary
|
||||
```
|
||||
|
||||
> :ToCPrevNext
|
@ -14,5 +14,3 @@ autorestic info
|
||||
```bash
|
||||
autorestic -c path/to/some/config.yml info
|
||||
```
|
||||
|
||||
> :ToCPrevNext
|
@ -5,5 +5,3 @@ Installs both restic and autorestic to `/usr/local/bin`.
|
||||
```bash
|
||||
autorestic install
|
||||
```
|
||||
|
||||
> :ToCPrevNext
|
17
docs/pages/cli/restore.md
Normal file
17
docs/pages/cli/restore.md
Normal file
@ -0,0 +1,17 @@
|
||||
# Restore
|
||||
|
||||
```bash
|
||||
autorestic restore [-l, --location] [--from backend] [--to <out dir>] [-f, --force] [snapshot]
|
||||
```
|
||||
|
||||
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`.
|
||||
|
||||
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
|
||||
|
||||
```bash
|
||||
autorestic restore -l home --from hdd --to /path/where/to/restore
|
||||
```
|
||||
|
||||
This will restore the location `home` to the `/path/where/to/restore` folder and taking the data from the backend `hdd`
|
@ -5,5 +5,3 @@ Uninstalls both restic and autorestic from `/usr/local/bin`.
|
||||
```bash
|
||||
autorestic uninstall
|
||||
```
|
||||
|
||||
> :ToCPrevNext
|
32
docs/pages/cli/unlock.md
Normal file
32
docs/pages/cli/unlock.md
Normal file
@ -0,0 +1,32 @@
|
||||
# Unlock
|
||||
|
||||
In case autorestic throws the error message `an instance is already running. exiting`, but there is no instance running you can unlock the lock.
|
||||
|
||||
To verify that there is no instance running you can use `ps aux | grep autorestic`.
|
||||
|
||||
Example with no instance running:
|
||||
|
||||
```bash
|
||||
> ps aux | grep autorestic
|
||||
root 39260 0.0 0.0 6976 2696 pts/11 S+ 19:41 0:00 grep autorestic
|
||||
```
|
||||
|
||||
Example with an instance running:
|
||||
|
||||
```bash
|
||||
> ps aux | grep autorestic
|
||||
root 29465 0.0 0.0 1162068 7380 pts/7 Sl+ 19:28 0:00 autorestic --ci backup -a
|
||||
root 39260 0.0 0.0 6976 2696 pts/11 S+ 19:41 0:00 grep autorestic
|
||||
```
|
||||
|
||||
**If an instance is running you should not unlock as it could lead to data loss!**
|
||||
|
||||
```bash
|
||||
autorestic unlock
|
||||
```
|
||||
|
||||
Use the `--force` to prevent the confirmation prompt if an instance is running.
|
||||
|
||||
```bash
|
||||
autorestic unlock --force
|
||||
```
|
@ -7,5 +7,3 @@ autorestic upgrade
|
||||
```
|
||||
|
||||
Updates both restic and autorestic automagically.
|
||||
|
||||
> :ToCPrevNext
|
17
docs/pages/community.md
Normal file
17
docs/pages/community.md
Normal file
@ -0,0 +1,17 @@
|
||||
# 🏘 Community
|
||||
|
||||
## Software
|
||||
|
||||
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>
|
||||
|
||||
## Writing
|
||||
|
||||
- [restic: excellent resource for local and cloud backup](https://notes.nicfab.eu/en/posts/restic/)
|
@ -2,10 +2,11 @@
|
||||
|
||||
## Path
|
||||
|
||||
By default autorestic searches for a `.autorestic.yml` file in the current directory and your home folder.
|
||||
By default autorestic searches for a `.autorestic.yml` file in the current directory, your home folder and your XDG config folder (`~/.config/` by default):
|
||||
|
||||
- `./.autorestic.yml`
|
||||
- `~/.autorestic.yml`
|
||||
- `~/.config/autorestic/.autorestic.yml`
|
||||
|
||||
You can also specify a custom file with the `-c path/to/some/config.yml`
|
||||
|
||||
@ -16,6 +17,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
|
||||
@ -49,8 +52,12 @@ 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
|
||||
prevalidate:
|
||||
- echo "Wake up!"
|
||||
before:
|
||||
- echo "Hello"
|
||||
after:
|
||||
@ -79,5 +86,3 @@ locations:
|
||||
forget:
|
||||
<<: *bar
|
||||
```
|
||||
|
||||
> :ToCPrevNext
|
19
docs/pages/contributors.md
Normal file
19
docs/pages/contributors.md
Normal file
@ -0,0 +1,19 @@
|
||||
# 🙋♀️🙋♂️ Contributors
|
||||
|
||||
This amazing people helped the project!
|
||||
|
||||
- @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.
|
28
docs/pages/docker.md
Normal file
28
docs/pages/docker.md
Normal 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 \\
|
||||
...
|
||||
```
|
38
docs/pages/examples.md
Normal file
38
docs/pages/examples.md
Normal file
@ -0,0 +1,38 @@
|
||||
# 🐣 Examples
|
||||
|
||||
## List all the snapshots for all the backends
|
||||
|
||||
```bash
|
||||
autorestic exec -av -- snapshots
|
||||
```
|
||||
|
||||
## Unlock a locked repository
|
||||
|
||||
This can come in handy if a backup process crashed or if it was accidentally cancelled. Then the repository would still be locked without an actual process using it. Only do this if you know what you are doing and are sure no other process is actually reading/writing to the repository of course.
|
||||
|
||||
```bash
|
||||
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
|
||||
```
|
@ -13,10 +13,8 @@ Autorestic is a wrapper around the amazing [restic](https://restic.net/). While
|
||||
- Backup locations to multiple backends
|
||||
- Snapshot policies and pruning
|
||||
- Fully encrypted
|
||||
- Pre/After hooks
|
||||
- Before/after backup hooks
|
||||
- Exclude pattern/files
|
||||
- Cron jobs for automatic backup
|
||||
- Backup & Restore docker volumes
|
||||
- Generated completions for `[bash|zsh|fish|powershell]`
|
||||
|
||||
> :ToCPrevNext
|
@ -5,11 +5,17 @@ Linux & macOS. Windows is not supported. If you have problems installing please
|
||||
Autorestic requires `bash`, `wget` and `bzip2` to be installed. For most systems these should be already installed.
|
||||
|
||||
```bash
|
||||
wget -qO - 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,10 @@ 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/) (looking for maintainers).
|
||||
|
||||
> :ToCPrevNext
|
||||
If you are on Arch there is an [AUR Package](https://aur.archlinux.org/packages/autorestic-bin/)
|
7
docs/pages/location/_meta.json
Normal file
7
docs/pages/location/_meta.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"index": "Overview",
|
||||
"hooks": "Hooks",
|
||||
"options": "Options",
|
||||
"cron": "Cronjobs",
|
||||
"docker": "Docker volumes"
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
# Cron
|
||||
|
||||
Often it is usefully to trigger backups automatically. For this we can specify a `cron` attribute to each location.
|
||||
Often it is useful to trigger backups automatically. For this, we can specify a `cron` attribute to each location.
|
||||
|
||||
```yaml | .autorestic.yml
|
||||
locations:
|
||||
@ -10,15 +10,15 @@ locations:
|
||||
cron: '0 3 * * 0' # Every Sunday at 3:00
|
||||
```
|
||||
|
||||
Here is a awesome website with [some examples](https://crontab.guru/examples.html) and an [explorer](https://crontab.guru/)
|
||||
Here is an awesome website with [some examples](https://crontab.guru/examples.html) and an [explorer](https://crontab.guru/).
|
||||
|
||||
## Installing the cron
|
||||
|
||||
**This has to be done only once, regardless of now many cron jobs you have in your config file.**
|
||||
**This has to be done only once, regardless of how many cron jobs you have in your config file.**
|
||||
|
||||
To actually enable cron jobs you need something to call `autorestic cron` on a timed schedule.
|
||||
Note that the schedule has nothing to do with the `cron` attribute in each location.
|
||||
My advise would be to trigger the command every 5min, but if you have a cronjob that runs only once a week, it's probably enough to schedule it once a day.
|
||||
My advice would be to trigger the command every 5min, but if you have a cronjob that runs only once a week, it's probably enough to schedule it once a day.
|
||||
|
||||
### Crontab
|
||||
|
||||
@ -50,6 +50,4 @@ To debug a cron job you can use
|
||||
|
||||
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.
|
||||
|
||||
> :ToCPrevNext
|
||||
> Also note that manually triggered backups with `autorestic backup` will not influence the cron timeline, they are intentionally not linked.
|
@ -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
|
||||
hello:
|
||||
from: my-data
|
||||
type: volume
|
||||
# ...
|
||||
```
|
||||
|
||||
@ -39,5 +35,3 @@ autorestic restore -l hello
|
||||
```
|
||||
|
||||
The volume has to exists whenever backing up or restoring.
|
||||
|
||||
> :ToCPrevNext
|
@ -6,23 +6,28 @@ They consist of a list of commands that will be executed in the same directory a
|
||||
|
||||
The following hooks groups are supported, none are required:
|
||||
|
||||
- `prevalidate`
|
||||
- `before`
|
||||
- `after`
|
||||
- `failure`
|
||||
- `success`
|
||||
|
||||
The difference between `prevalidate` and `before` hooks are that `prevalidate` is run before checking the backup location is valid, including checking that the `from` directories exist. This can be useful, for example, to mount the source filesystem that contains the directories listed in `from`.
|
||||
|
||||
```yml | .autorestic.yml
|
||||
locations:
|
||||
my-location:
|
||||
from: /data
|
||||
to: my-backend
|
||||
hooks:
|
||||
prevalidate:
|
||||
- echo "Checks"
|
||||
before:
|
||||
- echo "One"
|
||||
- echo "Two"
|
||||
- echo "Three"
|
||||
after:
|
||||
- echo "Byte"
|
||||
- echo "Bye"
|
||||
failure:
|
||||
- echo "Something went wrong"
|
||||
success:
|
||||
@ -31,13 +36,15 @@ locations:
|
||||
|
||||
## Flowchart
|
||||
|
||||
1. `before` hook
|
||||
2. Run backup
|
||||
3. `after` hook
|
||||
4. - `success` hook if no errors were found
|
||||
1. `prevalidate` hook
|
||||
2. Check backup location
|
||||
3. `before` hook
|
||||
4. Run backup
|
||||
5. `after` hook
|
||||
6. - `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.
|
||||
If either the `prevalidate` or `before` hook encounters errors then the backup and `after` hooks will be skipped and only the `failed` hooks will run.
|
||||
|
||||
## Environment variables
|
||||
|
||||
@ -76,5 +83,3 @@ AUTORESTIC_LOCATION=bar
|
||||
AUTORESTIC_FILES_ADDED_0=42
|
||||
AUTORESTIC_FILES_ADDED_FOO=42
|
||||
```
|
||||
|
||||
> :ToCPrevNext
|
@ -3,10 +3,18 @@
|
||||
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 +22,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?
|
||||
|
||||
@ -23,5 +31,3 @@ Paths can be absolute or relative. If relative they are resolved relative to the
|
||||
## `to`
|
||||
|
||||
This is either a single backend or an array of backends. The backends have to be configured in the same config file.
|
||||
|
||||
> :ToCPrevNext
|
3
docs/pages/location/options/_meta.json
Normal file
3
docs/pages/location/options/_meta.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"index": "Overview"
|
||||
}
|
31
docs/pages/location/options/copy.md
Normal file
31
docs/pages/location/options/copy.md
Normal 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`.
|
@ -16,5 +16,3 @@ locations:
|
||||
- '*.abc'
|
||||
exclude-file: .gitignore
|
||||
```
|
||||
|
||||
> :ToCPrevNext
|
@ -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,35 @@ 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
|
||||
```
|
||||
|
||||
> :ToCPrevNext
|
||||
## 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
|
||||
```
|
@ -26,7 +26,7 @@ locations:
|
||||
|
||||
## Example
|
||||
|
||||
In this example, whenever `autorestic` runs `restic backup` it will append a `--tag abc --tag` to the native command.
|
||||
In this example, whenever `autorestic` runs `restic backup` it will append a `--tag foo --tag bar` to the native command.
|
||||
|
||||
```yaml
|
||||
locations:
|
||||
@ -40,6 +40,12 @@ locations:
|
||||
- bar
|
||||
```
|
||||
|
||||
## 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.
|
||||
@ -57,5 +63,3 @@ backends:
|
||||
locations:
|
||||
# ...
|
||||
```
|
||||
|
||||
> :ToCPrevNext
|
4
docs/pages/migration.md
Normal file
4
docs/pages/migration.md
Normal 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)
|
@ -1,6 +1,4 @@
|
||||
# Upgrade
|
||||
|
||||
## 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.
|
||||
|
||||
@ -24,5 +22,3 @@ remote:
|
||||
```
|
||||
|
||||
Other than the config file there is a new `-v, --verbose` flag which shows the output of native commands, which are now hidden by default.
|
||||
|
||||
> :ToCPrevNext
|
66
docs/pages/migration/1.4_1.5.md
Normal file
66
docs/pages/migration/1.4_1.5.md
Normal file
@ -0,0 +1,66 @@
|
||||
# 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
|
||||
```
|
4
docs/pages/migration/_meta.json
Normal file
4
docs/pages/migration/_meta.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"0.x_1.0": "0.x → 1.0",
|
||||
"1.4_1.5": "1.4 → 1.5"
|
||||
}
|
@ -6,5 +6,3 @@ This happens when autorestic needs to write to the config file: e.g. when we are
|
||||
Unfortunately during this process formatting and comments are lost because the `yaml` library used is not comment and/or format aware.
|
||||
|
||||
That is why autorestic will place a copy of your old config next to the one we are writing to.
|
||||
|
||||
> :ToCPrevNext
|
@ -3,7 +3,7 @@
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
wget -qO - 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:
|
||||
@ -76,5 +82,3 @@ autorestic restore -l home --from hdd --to /path/where/to/restore
|
||||
```
|
||||
|
||||
This will restore the location `home` from the backend `hdd` to the given path.
|
||||
|
||||
> :ToCPrevNext
|
3190
docs/pnpm-lock.yaml
generated
Normal file
3190
docs/pnpm-lock.yaml
generated
Normal file
File diff suppressed because it is too large
Load Diff
24
docs/theme.config.jsx
Normal file
24
docs/theme.config.jsx
Normal file
@ -0,0 +1,24 @@
|
||||
export default {
|
||||
logo: <span>Autorestic</span>,
|
||||
docsRepositoryBase: 'https://github.com/cupcakearmy/autorestic/tree/master/docs',
|
||||
project: {
|
||||
link: 'https://github.com/cupcakearmy/autorestic',
|
||||
},
|
||||
sidebar: {
|
||||
defaultMenuCollapseLevel: 1,
|
||||
},
|
||||
feedback: {
|
||||
content: 'Question? An error? Give feedback →',
|
||||
},
|
||||
footer: {
|
||||
text: (
|
||||
<span>
|
||||
MIT {new Date().getFullYear()} ©{' '}
|
||||
<a href="https://github.com/cupcakearmy" target="_blank">
|
||||
cupcakearmy
|
||||
</a>
|
||||
.
|
||||
</span>
|
||||
),
|
||||
},
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
{
|
||||
"cleanUrls": true
|
||||
}
|
33
go.mod
33
go.mod
@ -1,14 +1,39 @@
|
||||
module github.com/cupcakearmy/autorestic
|
||||
|
||||
go 1.16
|
||||
go 1.21
|
||||
|
||||
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
|
||||
github.com/stretchr/testify v1.9.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
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/pmezard/go-difflib v1.0.0 // 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.1 // indirect
|
||||
)
|
||||
|
486
go.sum
486
go.sum
@ -3,216 +3,217 @@ 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/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg=
|
||||
github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
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/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/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
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=
|
||||
@ -222,16 +223,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=
|
||||
@ -240,20 +247,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=
|
||||
@ -261,43 +295,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=
|
||||
@ -307,24 +425,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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/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=
|
||||
|
11
install.sh
11
install.sh
@ -1,5 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e -o pipefail
|
||||
shopt -s nocaseglob
|
||||
|
||||
OUT_FILE=/usr/local/bin/autorestic
|
||||
@ -10,6 +10,8 @@ if [[ $NATIVE_OS == *"linux"* ]]; then
|
||||
OS=linux
|
||||
elif [[ $NATIVE_OS == *"darwin"* ]]; then
|
||||
OS=darwin
|
||||
elif [[ $NATIVE_OS == *"freebsd"* ]]; then
|
||||
OS=freebsd
|
||||
else
|
||||
echo "Could not determine OS automatically, please check the release page manually: https://github.com/cupcakearmy/autorestic/releases"
|
||||
exit 1
|
||||
@ -17,7 +19,7 @@ fi
|
||||
echo $OS
|
||||
|
||||
NATIVE_ARCH=$(uname -m | tr '[:upper:]' '[:lower:]')
|
||||
if [[ $NATIVE_ARCH == *"x86_64"* ]]; then
|
||||
if [[ $NATIVE_ARCH == *"x86_64"* || $NATIVE_ARCH == *"amd64"* ]]; then
|
||||
ARCH=amd64
|
||||
elif [[ $NATIVE_ARCH == *"arm64"* || $NATIVE_ARCH == *"aarch64"* ]]; then
|
||||
ARCH=arm64
|
||||
@ -31,6 +33,11 @@ else
|
||||
fi
|
||||
echo $ARCH
|
||||
|
||||
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 \
|
||||
|
@ -6,24 +6,27 @@ import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"os"
|
||||
"regexp"
|
||||
"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" yaml:"user,omitempty"`
|
||||
Password string `mapstructure:"password,omitempty" yaml:"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"`
|
||||
Options Options `yaml:"options,omitempty"`
|
||||
name string
|
||||
Type string `mapstructure:"type,omitempty" yaml:"type,omitempty"`
|
||||
Path string `mapstructure:"path,omitempty" yaml:"path,omitempty"`
|
||||
Key string `mapstructure:"key,omitempty" yaml:"key,omitempty"`
|
||||
RequireKey bool `mapstructure:"requireKey,omitempty" yaml:"requireKey,omitempty"`
|
||||
Env map[string]string `mapstructure:"env,omitempty" yaml:"env,omitempty"`
|
||||
Rest BackendRest `mapstructure:"rest,omitempty" yaml:"rest,omitempty"`
|
||||
Options Options `mapstructure:"options,omitempty" yaml:"options,omitempty"`
|
||||
}
|
||||
|
||||
func GetBackend(name string) (Backend, bool) {
|
||||
@ -37,7 +40,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
|
||||
}
|
||||
@ -56,6 +59,8 @@ func (b Backend) generateRepo() (string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
var nonAlphaRegex = regexp.MustCompile("[^A-Za-z0-9]")
|
||||
|
||||
func (b Backend) getEnv() (map[string]string, error) {
|
||||
env := make(map[string]string)
|
||||
// Key
|
||||
@ -71,7 +76,9 @@ func (b Backend) getEnv() (map[string]string, error) {
|
||||
}
|
||||
|
||||
// From Envfile and passed as env
|
||||
var prefix = "AUTORESTIC_" + strings.ToUpper(b.name) + "_"
|
||||
nameForEnv := strings.ToUpper(b.name)
|
||||
nameForEnv = nonAlphaRegex.ReplaceAllString(nameForEnv, "_")
|
||||
var prefix = "AUTORESTIC_" + nameForEnv + "_"
|
||||
for _, variable := range os.Environ() {
|
||||
var splitted = strings.SplitN(variable, "=", 2)
|
||||
if strings.HasPrefix(splitted[0], prefix) {
|
||||
@ -103,6 +110,9 @@ func (b Backend) validate() error {
|
||||
// Check if key is set in environment
|
||||
env, _ := b.getEnv()
|
||||
if _, found := env["RESTIC_PASSWORD"]; !found {
|
||||
if b.RequireKey {
|
||||
return fmt.Errorf("backend %s requires a key but none was provided", b.name)
|
||||
}
|
||||
// No key set in config file or env => generate random key and save file
|
||||
key := generateRandomKey()
|
||||
b.Key = key
|
||||
@ -119,18 +129,19 @@ func (b Backend) validate() error {
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -141,46 +152,64 @@ func (b Backend) Exec(args []string) error {
|
||||
return err
|
||||
}
|
||||
options := ExecuteOptions{Envs: env}
|
||||
out, err := ExecuteResticCommand(options, args...)
|
||||
args = append(args, combineBackendOptions("exec", b)...)
|
||||
_, 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":
|
||||
case "rest":
|
||||
// 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...)
|
||||
}
|
||||
|
265
internal/backend_test.go
Normal file
265
internal/backend_test.go
Normal file
@ -0,0 +1,265 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
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")
|
||||
})
|
||||
|
||||
for _, char := range "@-_:/" {
|
||||
t.Run(fmt.Sprintf("env var with special char (%c)", char), func(t *testing.T) {
|
||||
// generate env variables
|
||||
// TODO better way to teardown
|
||||
defer os.Unsetenv("AUTORESTIC_FOO_BAR_RESTIC_PASSWORD")
|
||||
defer os.Unsetenv("AUTORESTIC_FOO_BAR_B2_ACCOUNT_ID")
|
||||
defer os.Unsetenv("AUTORESTIC_FOO_BAR_B2_ACCOUNT_KEY")
|
||||
os.Setenv("AUTORESTIC_FOO_BAR_RESTIC_PASSWORD", "secret123")
|
||||
os.Setenv("AUTORESTIC_FOO_BAR_B2_ACCOUNT_ID", "foo123")
|
||||
os.Setenv("AUTORESTIC_FOO_BAR_B2_ACCOUNT_KEY", "foo456")
|
||||
|
||||
b := Backend{
|
||||
name: fmt.Sprintf("foo%cbar", char),
|
||||
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\"")
|
||||
})
|
||||
|
||||
t.Run("require key with no key", func(t *testing.T) {
|
||||
b := Backend{
|
||||
name: "foo",
|
||||
Type: "local",
|
||||
Path: "~/foo/bar",
|
||||
RequireKey: true,
|
||||
}
|
||||
err := b.validate()
|
||||
fmt.Printf("error: %v\n", err)
|
||||
assert.EqualError(t, err, "backend foo requires a key but none was provided")
|
||||
})
|
||||
}
|
@ -6,7 +6,6 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
"path"
|
||||
@ -16,6 +15,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"
|
||||
@ -36,7 +36,7 @@ func dlJSON(url string) (GithubRelease, error) {
|
||||
return parsed, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return parsed, err
|
||||
|
||||
@ -72,9 +72,10 @@ func downloadAndInstallAsset(body GithubRelease, name string) error {
|
||||
// Uncompress
|
||||
bz := bzip2.NewReader(resp.Body)
|
||||
|
||||
// Save to tmp
|
||||
// Linux does not support overwriting the file that is currently being overwritten, but it can be deleted and a new one moved in its place.
|
||||
tmp, err := ioutil.TempFile(os.TempDir(), "autorestic-")
|
||||
// Save to tmp file in the same directory as the install directory
|
||||
// Linux does not support overwriting the file that is currently being running
|
||||
// But it can be delete the old one and a new one moved in its place.
|
||||
tmp, err := os.CreateTemp(INSTALL_PATH, "autorestic-")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -87,9 +88,25 @@ func downloadAndInstallAsset(body GithubRelease, name string) error {
|
||||
}
|
||||
|
||||
to := path.Join(INSTALL_PATH, name)
|
||||
defer os.Remove(to) // Delete if current, ignore error if file does not exits.
|
||||
if err := os.Rename(tmp.Name(), to); err != nil {
|
||||
return nil
|
||||
defer os.Remove(tmp.Name()) // Cleanup temporary file after thread exits
|
||||
|
||||
mode := os.FileMode(0755)
|
||||
if originalBin, err := os.Lstat(to); err == nil {
|
||||
mode = originalBin.Mode()
|
||||
err := os.Remove(to)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = os.Rename(tmp.Name(), to)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = os.Chmod(to, mode)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
colors.Success.Printf("Successfully installed '%s' under %s\n", name, INSTALL_PATH)
|
||||
@ -114,10 +131,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
|
||||
}
|
||||
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"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"
|
||||
@ -16,48 +17,80 @@ import (
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
const VERSION = "1.4.1"
|
||||
|
||||
var CI bool = false
|
||||
var VERBOSE bool = false
|
||||
var CRON_LEAN bool = false
|
||||
const VERSION = "1.8.3"
|
||||
|
||||
type OptionMap map[string][]interface{}
|
||||
type Options map[string]OptionMap
|
||||
|
||||
type Config struct {
|
||||
Extras interface{} `yaml:"extras"`
|
||||
Locations map[string]Location `yaml:"locations"`
|
||||
Backends map[string]Backend `yaml:"backends"`
|
||||
Global Options `yaml:"global"`
|
||||
Version string `mapstructure:"version" yaml:"version"`
|
||||
Extras interface{} `mapstructure:"extras" yaml:"extras"`
|
||||
Locations map[string]Location `mapstructure:"locations" yaml:"locations"`
|
||||
Backends map[string]Backend `mapstructure:"backends" yaml:"backends"`
|
||||
Global Options `mapstructure:"global" yaml:"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 {
|
||||
absConfig, _ := filepath.Abs(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 {
|
||||
colors.Faint.Println("Using env:\t", envFile)
|
||||
}
|
||||
}
|
||||
// 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 {
|
||||
colors.Error.Println("Could not parse config file!")
|
||||
lock.Unlock()
|
||||
os.Exit(1)
|
||||
exitConfig(err, "Could not parse config file!")
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -81,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 {
|
||||
@ -95,10 +132,11 @@ func (c *Config) Describe() {
|
||||
|
||||
tmp = ""
|
||||
hooks := map[string][]string{
|
||||
"Before": l.Hooks.Before,
|
||||
"After": l.Hooks.After,
|
||||
"Failure": l.Hooks.Failure,
|
||||
"Success": l.Hooks.Success,
|
||||
"PreValidate": l.Hooks.PreValidate,
|
||||
"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 {
|
||||
@ -148,17 +186,33 @@ func CheckConfig() error {
|
||||
return fmt.Errorf("config could not be loaded/found")
|
||||
}
|
||||
if !CheckIfResticIsCallable() {
|
||||
return fmt.Errorf(`%s was not found. Install either with "autorestic install" or manually`, RESTIC_BIN)
|
||||
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
|
||||
if err := backend.validate(); err != nil {
|
||||
|
||||
cwd, _ := GetPathRelativeToConfig(".")
|
||||
for name, location := range c.Locations {
|
||||
location.name = name
|
||||
|
||||
// Hooks before location validation
|
||||
options := ExecuteOptions{
|
||||
Command: "bash",
|
||||
Dir: cwd,
|
||||
Envs: map[string]string{
|
||||
"AUTORESTIC_LOCATION": location.name,
|
||||
},
|
||||
}
|
||||
if err := location.ExecuteHooks(location.Hooks.PreValidate, options); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := location.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for name, location := range c.Locations {
|
||||
location.name = name
|
||||
if err := location.validate(); err != nil {
|
||||
|
||||
for name, backend := range c.Backends {
|
||||
backend.name = name
|
||||
if err := backend.validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -229,7 +283,7 @@ 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)
|
||||
@ -258,14 +312,32 @@ func appendOptionsToSlice(str *[]string, options OptionMap) {
|
||||
}
|
||||
}
|
||||
|
||||
func getOptions(options Options, key string) []string {
|
||||
func getOptions(options Options, keys []string) []string {
|
||||
var selected []string
|
||||
var keys = []string{"all"}
|
||||
if key != "" {
|
||||
keys = append(keys, key)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
211
internal/config_test.go
Normal file
211
internal/config_test.go
Normal file
@ -0,0 +1,211 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"path"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
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 TestSaveConfigProducesReadableConfig(t *testing.T) {
|
||||
workDir := t.TempDir()
|
||||
viper.SetConfigFile(path.Join(workDir, ".autorestic.yml"))
|
||||
|
||||
// Required to appease the config reader
|
||||
viper.Set("version", 2)
|
||||
|
||||
c := Config{
|
||||
Version: "2",
|
||||
Locations: map[string]Location{
|
||||
"test": {
|
||||
Type: "local",
|
||||
name: "test",
|
||||
From: []string{"in-dir"},
|
||||
To: []string{"test"},
|
||||
// ForgetOption & ConfigOption have previously marshalled in a way that
|
||||
// can't get read correctly
|
||||
ForgetOption: "foo",
|
||||
CopyOption: map[string][]string{"foo": {"bar"}},
|
||||
},
|
||||
},
|
||||
Backends: map[string]Backend{
|
||||
"test": {
|
||||
name: "test",
|
||||
Type: "local",
|
||||
Path: "backup-target",
|
||||
Key: "supersecret",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err := c.SaveConfig()
|
||||
assert.NoError(t, err)
|
||||
|
||||
// Ensure we the config reading logic actually runs
|
||||
config = nil
|
||||
once = sync.Once{}
|
||||
readConfig := GetConfig()
|
||||
assert.NotNil(t, readConfig)
|
||||
assert.Equal(t, c, *readConfig)
|
||||
}
|
||||
|
||||
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])
|
||||
}
|
||||
}
|
@ -1,12 +1,22 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func RunCron() error {
|
||||
c := GetConfig()
|
||||
var errs []error
|
||||
for name, l := range c.Locations {
|
||||
l.name = name
|
||||
if err := l.RunCron(); err != nil {
|
||||
return err
|
||||
errs = append(errs, err)
|
||||
}
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return fmt.Errorf("Encountered errors during cron process:\n%w", errors.Join(errs...))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
9
internal/flags/flags.go
Normal file
9
internal/flags/flags.go
Normal file
@ -0,0 +1,9 @@
|
||||
package flags
|
||||
|
||||
var (
|
||||
CI bool = false
|
||||
VERBOSE bool = false
|
||||
CRON_LEAN bool = false
|
||||
RESTIC_BIN string
|
||||
DOCKER_IMAGE string
|
||||
)
|
@ -1,6 +1,7 @@
|
||||
package internal
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
@ -9,6 +10,7 @@ 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"
|
||||
@ -17,27 +19,41 @@ import (
|
||||
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,omitempty"`
|
||||
After HookArray `yaml:"after,omitempty"`
|
||||
Success HookArray `yaml:"success,omitempty"`
|
||||
Failure HookArray `yaml:"failure,omitempty"`
|
||||
Dir string `mapstructure:"dir" yaml:"dir"`
|
||||
PreValidate HookArray `mapstructure:"prevalidate,omitempty" yaml:"prevalidate,omitempty"`
|
||||
Before HookArray `mapstructure:"before,omitempty" yaml:"before,omitempty"`
|
||||
After HookArray `mapstructure:"after,omitempty" yaml:"after,omitempty"`
|
||||
Success HookArray `mapstructure:"success,omitempty" yaml:"success,omitempty"`
|
||||
Failure HookArray `mapstructure:"failure,omitempty" yaml:"failure,omitempty"`
|
||||
}
|
||||
|
||||
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" yaml:",omitempty"`
|
||||
From []string `mapstructure:"from,omitempty" yaml:"from,omitempty"`
|
||||
Type string `mapstructure:"type,omitempty" yaml:"type,omitempty"`
|
||||
To []string `mapstructure:"to,omitempty" yaml:"to,omitempty"`
|
||||
Hooks Hooks `mapstructure:"hooks,omitempty" yaml:"hooks,omitempty"`
|
||||
Cron string `mapstructure:"cron,omitempty" yaml:"cron,omitempty"`
|
||||
Options Options `mapstructure:"options,omitempty" yaml:"options,omitempty"`
|
||||
ForgetOption LocationForgetOption `mapstructure:"forget,omitempty" yaml:"forget,omitempty"`
|
||||
CopyOption LocationCopy `mapstructure:"copy,omitempty" yaml:"copy,omitempty"`
|
||||
}
|
||||
|
||||
func GetLocation(name string) (Location, bool) {
|
||||
@ -47,108 +63,142 @@ func GetLocation(name string) (Location, bool) {
|
||||
}
|
||||
|
||||
func (l Location) validate() error {
|
||||
if l.From == "" {
|
||||
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 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, specificBackend string) []error {
|
||||
func (l Location) Backup(cron bool, dry 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 err := l.validate(); err != nil {
|
||||
// Hooks before location validation
|
||||
if err := l.ExecuteHooks(l.Hooks.PreValidate, options); err != nil {
|
||||
errors = append(errors, err)
|
||||
colors.Error.Print(err)
|
||||
goto after
|
||||
}
|
||||
|
||||
if t == TypeLocal {
|
||||
dir, _ := GetPathRelativeToConfig(l.From)
|
||||
colors.Faint.Printf("Executing under: \"%s\"\n", dir)
|
||||
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 {
|
||||
// Hooks after location validation
|
||||
if err := l.ExecuteHooks(l.Hooks.Before, options); err != nil {
|
||||
errors = append(errors, err)
|
||||
goto after
|
||||
}
|
||||
@ -173,74 +223,124 @@ func (l Location) Backup(cron bool, specificBackend string) []error {
|
||||
continue
|
||||
}
|
||||
|
||||
lFlags := getOptions(l.Options, "backup")
|
||||
bFlags := getOptions(backend.Options, "backup")
|
||||
cmd := []string{"backup"}
|
||||
cmd = append(cmd, lFlags...)
|
||||
cmd = append(cmd, bFlags...)
|
||||
cmd = append(cmd, combineAllOptions("backup", l, backend)...)
|
||||
if cron {
|
||||
cmd = append(cmd, "--tag", "cron")
|
||||
cmd = append(cmd, "--tag", buildTag("cron"))
|
||||
}
|
||||
cmd = append(cmd, ".")
|
||||
if dry {
|
||||
cmd = append(cmd, "--dry-run")
|
||||
}
|
||||
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)
|
||||
}
|
||||
if err != nil {
|
||||
colors.Error.Println(out)
|
||||
errors = append(errors, err)
|
||||
continue
|
||||
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 VERBOSE {
|
||||
colors.Faint.Println(out)
|
||||
|
||||
// If error save it and continue
|
||||
if err != nil {
|
||||
colors.Error.Println(out)
|
||||
errors = append(errors, fmt.Errorf("%s@%s:\n%s%s", l.name, backend.name, out, err))
|
||||
continue
|
||||
}
|
||||
|
||||
// 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 {
|
||||
// After backup hooks
|
||||
if err := l.ExecuteHooks(l.Hooks.After, options); err != nil {
|
||||
errors = append(errors, err)
|
||||
}
|
||||
|
||||
after:
|
||||
// Success/failure hooks
|
||||
var commands []string
|
||||
if len(errors) > 0 {
|
||||
commands = l.Hooks.Failure
|
||||
} else {
|
||||
var isSuccess = len(errors) == 0
|
||||
if isSuccess {
|
||||
commands = l.Hooks.Success
|
||||
} else {
|
||||
commands = l.Hooks.Failure
|
||||
}
|
||||
if err := ExecuteHooks(commands, options); err != nil {
|
||||
if err := l.ExecuteHooks(commands, options); err != nil {
|
||||
errors = append(errors, err)
|
||||
}
|
||||
|
||||
colors.Success.Println("Done")
|
||||
// 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()
|
||||
@ -250,21 +350,15 @@ func (l Location) Forget(prune bool, dry bool) error {
|
||||
options := ExecuteOptions{
|
||||
Envs: env,
|
||||
}
|
||||
lFlags := getOptions(l.Options, "forget")
|
||||
bFlags := getOptions(backend.Options, "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, lFlags...)
|
||||
cmd = append(cmd, bFlags...)
|
||||
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
|
||||
}
|
||||
@ -282,28 +376,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)
|
||||
@ -322,9 +425,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
|
||||
@ -347,9 +450,12 @@ func (l Location) RunCron() error {
|
||||
now := time.Now()
|
||||
if now.After(next) {
|
||||
lock.SetCron(l.name, now.Unix())
|
||||
l.Backup(true, "")
|
||||
errs := l.Backup(true, false, "")
|
||||
if len(errs) > 0 {
|
||||
return fmt.Errorf("Failed to backup location \"%s\":\n%w", l.name, errors.Join(errs...))
|
||||
}
|
||||
} 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
93
internal/location_test.go
Normal 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)
|
||||
}
|
@ -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
114
internal/lock/lock_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
@ -14,9 +14,9 @@ func (e addedExtractor) Matches(line string) bool {
|
||||
}
|
||||
func (e addedExtractor) Extract(metadata *BackupLogMetadata, line string) {
|
||||
// Sample line: "Added to the repo: 0 B"
|
||||
metadata.AddedSize = strings.TrimSpace(e.re.ReplaceAllString(line, ""))
|
||||
metadata.AddedSize = strings.TrimSpace(e.re.ReplaceAllString(line, "$2"))
|
||||
}
|
||||
|
||||
func NewAddedExtractor() MetadatExtractor {
|
||||
return addedExtractor{regexp.MustCompile(`(?i)^Added to the repo:`)}
|
||||
return addedExtractor{regexp.MustCompile(`(?i)^Added to the repo(sitory)?: ([\d\.]+ \w+)( \([\d\.]+[\w\s]+\))?`)}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user