Compare commits

...

30 Commits

Author SHA1 Message Date
ddc3accb30 Merge pull request #114 from cupcakearmy/1.3.0
1.3.0
2021-10-26 16:03:09 +02:00
7874512ec0 changelog 2021-10-26 16:02:30 +02:00
0b5f4017e4 version bump 2021-10-26 15:59:49 +02:00
88198c4fcb docs for hook envs 2021-10-26 15:57:47 +02:00
8cd759105f env for hooks 2021-10-26 15:57:40 +02:00
6137e31c3b allow argumentless flags 2021-10-25 18:29:59 +02:00
2789502c89 toc 2021-10-25 18:02:56 +02:00
b87381cd3b docs for envs 2021-10-25 18:02:51 +02:00
048a5ffed8 renamed env 2021-10-25 18:02:45 +02:00
02a8e461d4 ability to use keys from envs 2021-10-25 17:36:06 +02:00
a1abe13a39 docs on cron 2021-10-25 17:07:29 +02:00
ddf287f6f5 Merge pull request #113 from cupcakearmy/1.3.0
1.3.0
2021-10-22 17:59:25 +02:00
56f82ae656 pnpm & typo 2021-10-22 17:57:43 +02:00
95b1ca3297 Merge pull request #108 from FuzzyMistborn/patch-2
Add third ansible role to community docs
2021-10-07 12:14:08 +02:00
Fuzzy
7a8830cb2f Add third ansible role to community docs 2021-10-06 13:24:40 -04:00
959d19cbdb add XDG_CONFIG_HOME to config 2021-10-06 15:50:23 +02:00
9dc7763445 Merge pull request #103 from IronicBadger/master
fixes small typo
2021-09-01 10:31:53 +02:00
Alex Kretzschmar
50984f6771 typo 2021-08-31 18:35:11 -04:00
e80f200873 systemd units 2021-08-06 10:20:40 +02:00
86ae70672a right version 2021-08-05 21:58:48 +02:00
5c0788900f docs 2021-08-05 21:54:40 +02:00
20334a7e83 better config handling 2021-08-05 21:48:02 +02:00
7bebd04482 validate and show cwd 2021-07-12 19:10:36 +02:00
b9b8857bf4 aliases 2021-07-12 19:10:25 +02:00
dd6e618161 typo 2021-07-11 14:03:04 +02:00
a4b54f9f64 check config bugs 2021-07-11 13:51:04 +02:00
c2e88193cd Update contrib.md 2021-06-06 15:51:50 +02:00
a2ef69d96d Merge pull request #87 from somebox/patch-1
correction to B2 config in the example
2021-06-06 15:48:02 +02:00
Jeremy Seitz
d45949b028 correction to B2 config in the example
This example was missing the `env:` block in YAML, which if missing, caused `autorestic check` to compain:

``` * 'Backends[remote]' has invalid keys: b2_account_id, b2_account_key```
2021-06-06 14:27:26 +02:00
77d47cc697 add arm in install script 2021-05-25 22:57:42 +02:00
30 changed files with 599 additions and 831 deletions

View File

@@ -5,6 +5,33 @@ 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.3.0] - 2021-10-26
### Added
- Pass restic backup metadata as ENV to hooks
- Support for `XDG_CONFIG_HOME` and `${HOME}/.config` as default locations for `.autorestic.yaml` file.
- Binary restic flags are now supported
- Pass encryption keys from env variables or files.
## [1.2.0] - 2021-08-05
### Added
- Community page
- Support for yaml references and aliases
### Fixed
- Better verbose output for hooks
- Better error message for bad formatted configs
## [1.1.2] - 2021-07-11
### Fixes
Don't check all backend when running `forget` or `exec` commands.
## [1.1.1] - 2021-05-17
### Added

View File

@@ -19,7 +19,7 @@ Releases are automatically built by the github workflow and uploaded to the rele
1. Bump `VERSION` in `internal/config.go`.
2. Update `CHANGELOG.md`
3. Commit to master
4. Create a new release with the `v1.2.3` tag and mark as draft.
4. Create a new release with the `v1.2.3` tag and mark as pre-release.
5. The Github action will build the binaries, upload and mark the release as ready when done.
### Brew

View File

@@ -25,7 +25,7 @@
### 💭 Why / What?
Autorestic is a wrapper around the amazing [restic](https://restic.net/). While being amazing the restic cli can be a bit overwhelming and difficult to manage if you have many different location that you want to backup to multiple locations. This utility is aimed at making this easier 🙂
Autorestic is a wrapper around the amazing [restic](https://restic.net/). While being amazing the restic cli can be a bit overwhelming and difficult to manage if you have many different locations that you want to backup to multiple locations. This utility is aimed at making this easier 🙂.
### 🌈 Features

View File

@@ -13,12 +13,11 @@ var backupCmd = &cobra.Command{
Use: "backup",
Short: "Create backups for given locations",
Run: func(cmd *cobra.Command, args []string) {
internal.GetConfig()
err := lock.Lock()
CheckErr(err)
defer lock.Unlock()
internal.GetConfig()
selected, err := internal.GetAllOrSelected(cmd, false)
CheckErr(err)
errors := 0

View File

@@ -11,12 +11,11 @@ var execCmd = &cobra.Command{
Use: "exec",
Short: "Execute arbitrary native restic commands for given backends",
Run: func(cmd *cobra.Command, args []string) {
internal.GetConfig()
err := lock.Lock()
CheckErr(err)
defer lock.Unlock()
CheckErr(internal.CheckConfig())
selected, err := internal.GetAllOrSelected(cmd, true)
CheckErr(err)
for _, name := range selected {

View File

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

View File

@@ -2,6 +2,7 @@ package cmd
import (
"os"
"path/filepath"
"github.com/cupcakearmy/autorestic/internal"
"github.com/cupcakearmy/autorestic/internal/colors"
@@ -50,11 +51,24 @@ func initConfig() {
if cfgFile != "" {
viper.SetConfigFile(cfgFile)
} else {
home, err := homedir.Dir()
CheckErr(err)
viper.AddConfigPath(".")
viper.AddConfigPath(home)
// Home
if home, err := homedir.Dir(); err != nil {
viper.AddConfigPath(home)
}
// XDG_CONFIG_HOME
{
prefix, found := os.LookupEnv("XDG_CONFIG_HOME")
if !found {
if home, err := homedir.Dir(); err != nil {
prefix = filepath.Join(home, ".config")
}
}
viper.AddConfigPath(filepath.Join(prefix, "autorestic"))
}
viper.SetConfigName(".autorestic")
}
viper.AutomaticEnv()

View File

@@ -23,6 +23,7 @@
> [Overview](/backend/overview)
> [Available Backends](/backend/available)
> [Options](/backend/options)
> [Environment](/backend/env)
> :Collapse label=CLI
>
@@ -40,7 +41,7 @@
> [Upgrade](/cli/upgrade)
[Examples](/examples)
[QA](/qa)
[Community](/community)
[Contributors](/contrib)

View File

@@ -19,9 +19,9 @@ backends:
backends:
name-of-backend:
type: b2
path: 'backblaze_bucketID'
path: 'bucket_name'
# Or With a path
# path: 'backblaze_bucketID:/some/path'
# path: 'bucket_name:/some/path'
env:
B2_ACCOUNT_ID: 'backblaze_keyID'
B2_ACCOUNT_KEY: 'backblaze_applicationKey'
@@ -29,7 +29,7 @@ backends:
#### API Keys gotcha
If you use a _File name prefix_ when making the application key, do not include a leading slash. Make sure to include this prefix in the path (e.g. `path: 'backblaze_bucketID:my/path'`).
If you use a _File name prefix_ when making the application key, do not include a leading slash. Make sure to include this prefix in the path (e.g. `path: 'bucket_name:my/path'`).
## S3 / Minio

View File

@@ -0,0 +1,36 @@
# Environment
> ⚠ Available since version `v1.3.0`
Sometimes it's favorable not having the encryption keys in the config files.
For that `autorestic` allows passing the backend keys as `ENV` variables, or through an env file.
The syntax for the `ENV` variables is as follows: `AUTORESTIC_[BACKEND NAME]_KEY`.
```yaml | autorestic.yaml
backend:
foo:
type: ...
path: ...
key: secret123 # => AUTORESTIC_FOO_KEY=secret123
```
## Example
This means we could remove `key: secret123` from `.autorestic.yaml` and execute as follows:
```bash
AUTORESTIC_FOO_KEY=secret123 autorestic backup ...
```
## Env file
Alternatively `autorestic` can load an env file, located next to `autorestic.yml` called `.autorestic.env`.
```| .autorestic.env
AUTORESTIC_FOO_KEY=secret123
```
after that you can simply use `autorestic` as your are used to.
> :ToCPrevNext

View File

@@ -18,6 +18,8 @@ backend:
In this example, whenever `autorestic` runs `restic backup` it will append a `--tag abc --tag` to the native command.
For more detail see the [location docs](/location/options) for options, as they are the same
For more detail see the [location docs](/location/options) for options, as they are the same.
> For flags without arguments you can set them to `true`. They will be handled accordingly.
> :ToCPrevNext

View File

@@ -0,0 +1,11 @@
# 🏘 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

View File

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

View File

@@ -12,5 +12,6 @@ This amazing people helped the project!
- @ChanceM - Typos
- @TheForcer - Typos
- @themorlan - Typos
- @somebox - Typos
> :ToCPrevNext

View File

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

View File

@@ -35,8 +35,46 @@ locations:
2. Run backup
3. `after` hook
4. - `success` hook if no errors were found
- `failure` hook if at least error was encountered
- `failure` hook if at least one error was encountered
If the `before` hook encounters errors the backup and `after` hooks will be skipped and only the `failed` hooks will run.
## Environment variables
All hooks are exposed to the `AUTORESTIC_LOCATION` environment variable, which contains the location name.
The `after` and `success` hooks have access to additional information with the following syntax:
```bash
AUTORESTIC_[TYPE]_[I]
AUTORESTIC_[TYPE]_[BACKEND_NAME]
```
Every type of metadata is appended with both the name of the backend associated with and the number in which the backends where executed.
### Available Metadata Types
- `SNAPSHOT_ID`
- `PARENT_SNAPSHOT_ID`
- `FILES_ADDED`
- `FILES_CHANGED`
- `FILES_UNMODIFIED`
- `DIRS_ADDED`
- `DIRS_CHANGED`
- `DIRS_UNMODIFIED`
- `ADDED_SIZE`
- `PROCESSED_FILES`
- `PROCESSED_SIZE`
- `PROCESSED_DURATION`
#### Example
Assuming you have a location `bar` that backs up to a single backend named `foo` you could expect the following env variables:
```bash
AUTORESTIC_LOCATION=bar
AUTORESTIC_FILES_ADDED_0=42
AUTORESTIC_FILES_ADDED_FOO=42
```
> :ToCPrevNext

View File

@@ -18,4 +18,6 @@ locations:
In this example, whenever `autorestic` runs `restic backup` it will append a `--tag abc --tag` to the native command.
> For flags without arguments you can set them to `true`. They will be handled accordingly.
> :ToCPrevNext

878
docs/package-lock.json generated

File diff suppressed because it is too large Load Diff

1
go.mod
View File

@@ -6,6 +6,7 @@ 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/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

2
go.sum
View File

@@ -101,6 +101,8 @@ github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2p
github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
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=

View File

@@ -23,6 +23,8 @@ elif [[ $NATIVE_ARCH == *"arm64"* || $NATIVE_ARCH == *"aarch64"* ]]; then
ARCH=arm64
elif [[ $NATIVE_ARCH == *"x86"* ]]; then
ARCH=386
elif [[ $NATIVE_ARCH == *"armv7"* ]]; then
ARCH=arm
else
echo "Could not determine Architecure automatically, please check the release page manually: https://github.com/cupcakearmy/autorestic/releases"
exit 1

View File

@@ -58,7 +58,15 @@ func (b Backend) generateRepo() (string, error) {
func (b Backend) getEnv() (map[string]string, error) {
env := make(map[string]string)
env["RESTIC_PASSWORD"] = b.Key
if b.Key != "" {
env["RESTIC_PASSWORD"] = b.Key
} else {
key, err := b.getKey()
if err != nil {
return nil, err
}
env["RESTIC_PASSWORD"] = key
}
repo, err := b.generateRepo()
env["RESTIC_REPOSITORY"] = repo
for key, value := range b.Env {
@@ -77,6 +85,17 @@ func generateRandomKey() string {
return key
}
func (b Backend) getKey() (string, error) {
if b.Key != "" {
return b.Key, nil
}
keyName := "AUTORESTIC_" + strings.ToUpper(b.name) + "_KEY"
if key, found := os.LookupEnv(keyName); found {
return key, nil
}
return "", fmt.Errorf("no key found for backend \"%s\"", b.name)
}
func (b Backend) validate() error {
if b.Type == "" {
return fmt.Errorf(`Backend "%s" has no "type"`, b.name)
@@ -85,14 +104,18 @@ func (b Backend) validate() error {
return fmt.Errorf(`Backend "%s" has no "path"`, b.name)
}
if b.Key == "" {
key := generateRandomKey()
b.Key = key
c := GetConfig()
tmp := c.Backends[b.name]
tmp.Key = key
c.Backends[b.name] = tmp
if err := c.SaveConfig(); err != nil {
return err
// Check if key is set in environment
if _, err := b.getKey(); err != nil {
// If not generate a new one
key := generateRandomKey()
b.Key = key
c := GetConfig()
tmp := c.Backends[b.name]
tmp.Key = key
c.Backends[b.name] = tmp
if err := c.SaveConfig(); err != nil {
return err
}
}
}
env, err := b.getEnv()

View File

@@ -2,23 +2,28 @@ package internal
import (
"fmt"
"os"
"path"
"path/filepath"
"strings"
"sync"
"github.com/cupcakearmy/autorestic/internal/colors"
"github.com/cupcakearmy/autorestic/internal/lock"
"github.com/joho/godotenv"
"github.com/mitchellh/go-homedir"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
const VERSION = "1.1.1"
const VERSION = "1.3.0"
var CI bool = false
var VERBOSE bool = false
var CRON_LEAN bool = false
type Config struct {
Extras interface{} `yaml:"extras"`
Locations map[string]Location `yaml:"locations"`
Backends map[string]Backend `yaml:"backends"`
}
@@ -31,7 +36,14 @@ func GetConfig() *Config {
once.Do(func() {
if err := viper.ReadInConfig(); err == nil {
if !CRON_LEAN {
colors.Faint.Println("Using config file:", viper.ConfigFileUsed())
absConfig, _ := filepath.Abs(viper.ConfigFileUsed())
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)
}
}
} else {
return
@@ -39,7 +51,9 @@ func GetConfig() *Config {
config = &Config{}
if err := viper.UnmarshalExact(config); err != nil {
panic(err)
colors.Error.Println("Could not parse config file!")
lock.Unlock()
os.Exit(1)
}
})
}
@@ -140,7 +154,7 @@ func CheckConfig() error {
}
for name, location := range c.Locations {
location.name = name
if err := location.validate(c); err != nil {
if err := location.validate(); err != nil {
return err
}
}
@@ -225,7 +239,18 @@ func getOptions(options Options, key string) []string {
var selected []string
for k, values := range options[key] {
for _, value := range values {
selected = append(selected, fmt.Sprintf("--%s", k), value)
// Bool
asBool, ok := value.(bool)
if ok && asBool {
selected = append(selected, fmt.Sprintf("--%s", k))
continue
}
// String
asString, ok := value.(string)
if ok {
selected = append(selected, fmt.Sprintf("--%s", k), asString)
continue
}
}
}
return selected

View File

@@ -10,6 +10,7 @@ import (
"github.com/cupcakearmy/autorestic/internal/colors"
"github.com/cupcakearmy/autorestic/internal/lock"
"github.com/cupcakearmy/autorestic/internal/metadata"
"github.com/robfig/cron"
)
@@ -30,7 +31,7 @@ type Hooks struct {
Failure HookArray `yaml:"failure,omitempty"`
}
type Options map[string]map[string][]string
type Options map[string]map[string][]interface{}
type Location struct {
name string `yaml:",omitempty"`
@@ -47,7 +48,7 @@ func GetLocation(name string) (Location, bool) {
return l, ok
}
func (l Location) validate(c *Config) error {
func (l Location) validate() error {
if l.From == "" {
return fmt.Errorf(`Location "%s" is missing "from" key`, l.name)
}
@@ -130,10 +131,20 @@ func (l Location) Backup(cron bool) []error {
t := l.getType()
options := ExecuteOptions{
Command: "bash",
Envs: map[string]string{
"AUTORESTIC_LOCATION": l.name,
},
}
if err := l.validate(); 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
}
@@ -144,7 +155,7 @@ func (l Location) Backup(cron bool) []error {
}
// Backup
for _, to := range l.To {
for i, to := range l.To {
backend, _ := GetBackend(to)
colors.Secondary.Printf("Backend: %s\n", backend.name)
env, err := backend.getEnv()
@@ -180,6 +191,13 @@ func (l Location) Backup(cron bool) []error {
errors = append(errors, err)
continue
}
md := metadata.ExtractMetadataFromBackupLog(out)
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)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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