Skip to content

Commit

Permalink
New backblaze Storage Plugin
Browse files Browse the repository at this point in the history
The new Backblaze leverages their service B2 to upload files via the Go
library "github.com/kurin/blaze".
  • Loading branch information
dmolik authored and jhunt committed Aug 12, 2018
1 parent 159409b commit 51a95e7
Show file tree
Hide file tree
Showing 51 changed files with 14,408 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -13,6 +13,7 @@

# ignore the plugin
/azure
/backblaze
/bbr-deployment
/bbr-director
/cassandra
Expand Down
299 changes: 299 additions & 0 deletions plugin/backblaze/plugin.go
@@ -0,0 +1,299 @@
package main

import (
"context"
"io"
"os"
"regexp"
"strings"
"time"

fmt "github.com/jhunt/go-ansi"
"github.com/kurin/blazer/b2"

"github.com/starkandwayne/shield/plugin"
)

const (
DefaultPrefix = ""
DefaultUseInstanceProfiles = false
)

func validBucketName(v string) bool {
ok, err := regexp.MatchString(`^[a-z0-9][a-z0-9\.\-]{1,61}[a-z0-9]$`, v)
return ok && err == nil
}

func main() {
p := BackblazePlugin{
Name: "Backblaze Storage Plugin",
Author: "Stark & Wayne",
Version: "0.0.1",
Features: plugin.PluginFeatures{
Target: "no",
Store: "yes",
},
Example: `
{
"access_key_id" : "your-access-key-id", # REQUIRED
"secret_access_key" : "your-secret-access-key", # REQUIRED
"bucket" : "name-of-your-bucket", # REQUIRED
"prefix" : "/path/in/bucket", # where to store archives, inside the bucket
}
`,
Defaults: `
{
}
`,
Fields: []plugin.Field{
plugin.Field{
Mode: "store",
Name: "access_key_id",
Type: "string",
Title: "Access Key ID",
Help: "The Access Key ID to use when authenticating against B2.",
},
plugin.Field{
Mode: "store",
Name: "secret_access_key",
Type: "password",
Title: "Secret Access Key",
Help: "The Secret Access Key to use when authenticating against B2.",
},
plugin.Field{
Mode: "store",
Name: "bucket",
Type: "string",
Title: "Bucket Name",
Help: "Name of the bucket to store backup archives in.",
Example: "my-aws-backups",
Required: true,
},
plugin.Field{
Mode: "store",
Name: "prefix",
Type: "string",
Title: "Bucket Path Prefix",
Help: "An optional sub-path of the bucket to use for storing archives. By default, archives are stored in the root of the bucket.",
},
},
}

plugin.Run(p)
}

type BackblazePlugin plugin.PluginInfo

type backblazeEndpoint struct {
AccessKey string
SecretKey string
PathPrefix string
Bucket string
}

type instanceProfileCredentials struct {
Key string `json:"AccessKeyId"`
Secret string `json:"SecretAccessKey"`
}

func (p BackblazePlugin) Meta() plugin.PluginInfo {
return plugin.PluginInfo(p)
}

func (p BackblazePlugin) Validate(endpoint plugin.ShieldEndpoint) error {
var (
s string
err error
fail bool
)

//BEGIN AUTH VALIDATION
s, err = endpoint.StringValue("access_key_id")
if err != nil {
fmt.Printf("@R{\u2717 access_key_id %s}\n", err)
fail = true
} else {
fmt.Printf("@G{\u2713 access_key_id} @C{%s}\n", plugin.Redact(s))
}

s, err = endpoint.StringValue("secret_access_key")
if err != nil {
fmt.Printf("@R{\u2717 secret_access_key %s}\n", err)
fail = true
} else {
fmt.Printf("@G{\u2713 secret_access_key} @C{%s}\n", plugin.Redact(s))
}
//END AUTH VALIDATION

s, err = endpoint.StringValue("bucket")
if err != nil {
fmt.Printf("@R{\u2717 bucket %s}\n", err)
fail = true
} else if !validBucketName(s) {
fmt.Printf("@R{\u2717 bucket '%s' is an invalid bucket name (must be all lowercase)}\n", s)
fail = true
} else {
fmt.Printf("@G{\u2713 bucket} @C{%s}\n", plugin.Redact(s))
}

s, err = endpoint.StringValueDefault("prefix", DefaultPrefix)
if err != nil {
fmt.Printf("@R{\u2717 prefix %s}\n", err)
fail = true
} else if s == "" {
fmt.Printf("@G{\u2713 prefix} (none)\n")
} else {
s = strings.TrimLeft(s, "/")
fmt.Printf("@G{\u2713 prefix} @C{%s}\n", s)
}

if fail {
return fmt.Errorf("b2: invalid configuration")
}
return nil
}

func (p BackblazePlugin) Backup(endpoint plugin.ShieldEndpoint) error {
return plugin.UNIMPLEMENTED
}

func (p BackblazePlugin) Restore(endpoint plugin.ShieldEndpoint) error {
return plugin.UNIMPLEMENTED
}

func (p BackblazePlugin) Store(endpoint plugin.ShieldEndpoint) (string, int64, error) {
c, err := getB2ConnInfo(endpoint)
if err != nil {
return "", 0, err
}

client, err := c.Connect()
if err != nil {
return "", 0, err
}

path := c.genBackupPath()
plugin.DEBUG("Storing data in %s", path)

ctx := context.Background()
bucket, err := client.Bucket(ctx, c.Bucket)
if err != nil {
return "", 0, err
}
obj := bucket.Object(strings.TrimPrefix(path, "/"))
w := obj.NewWriter(ctx)
io.Copy(w, os.Stdin)

if err != nil {
return "", 0, err
}

w.Close()
if err != nil {
return "", 0, err
}

var size int64
size = 1024

return path, size, nil
}

func (p BackblazePlugin) Retrieve(endpoint plugin.ShieldEndpoint, file string) error {
e, err := getB2ConnInfo(endpoint)
if err != nil {
return err
}

client, err := e.Connect()
if err != nil {
return err
}

ctx := context.Background()
bucket, err := client.Bucket(ctx, e.Bucket)
if err != nil {
return err
}
obj := bucket.Object(strings.TrimPrefix(file, "/"))
reader := obj.NewReader(ctx)
if err != nil {
return err
}

_, err = io.Copy(os.Stdout, reader)
return err
}

func (p BackblazePlugin) Purge(endpoint plugin.ShieldEndpoint, file string) error {
e, err := getB2ConnInfo(endpoint)
if err != nil {
return err
}

client, err := e.Connect()
if err != nil {
return err
}
ctx := context.Background()
bucket, err := client.Bucket(ctx, e.Bucket)
if err != nil {
return err
}
obj := bucket.Object(strings.TrimPrefix(file, "/"))

return obj.Delete(ctx)
}

func getB2ConnInfo(e plugin.ShieldEndpoint) (backblazeEndpoint, error) {
var (
key string
secret string
err error
)

key, err = e.StringValue("access_key_id")
if err != nil {
return backblazeEndpoint{}, err
}

secret, err = e.StringValue("secret_access_key")
if err != nil {
return backblazeEndpoint{}, err
}

bucket, err := e.StringValue("bucket")
if err != nil {
return backblazeEndpoint{}, err
}

prefix, err := e.StringValueDefault("prefix", DefaultPrefix)
if err != nil {
return backblazeEndpoint{}, err
}
prefix = strings.TrimLeft(prefix, "/")

return backblazeEndpoint{
AccessKey: key,
SecretKey: secret,
PathPrefix: prefix,
Bucket: bucket,
}, nil
}

func (e backblazeEndpoint) genBackupPath() string {
t := time.Now()
year, mon, day := t.Date()
hour, min, sec := t.Clock()
uuid := plugin.GenUUID()
path := fmt.Sprintf("%s/%04d/%02d/%02d/%04d-%02d-%02d-%02d%02d%02d-%s", e.PathPrefix, year, mon, day, year, mon, day, hour, min, sec, uuid)
// Remove double slashes
path = strings.Replace(path, "//", "/", -1)
return path
}

func (e backblazeEndpoint) Connect() (*b2.Client, error) {
ctx := context.Background()
return b2.NewClient(ctx, e.AccessKey, e.SecretKey)
}
1 change: 1 addition & 0 deletions plugins
@@ -1,4 +1,5 @@
azure
backblaze
bbr-deployment
bbr-director
cassandra
Expand Down
26 changes: 26 additions & 0 deletions vendor/github.com/kurin/blazer/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions vendor/github.com/kurin/blazer/.travis.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

27 changes: 27 additions & 0 deletions vendor/github.com/kurin/blazer/CONTRIBUTING.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 13 additions & 0 deletions vendor/github.com/kurin/blazer/LICENSE

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

0 comments on commit 51a95e7

Please sign in to comment.