From f7acc7fffdff06c0df2cc7d81a99e4ce6e118027 Mon Sep 17 00:00:00 2001 From: David Chung Date: Thu, 4 May 2017 16:39:54 -0700 Subject: [PATCH 1/3] add -o option to write template processing to file Signed-off-by: David Chung --- cmd/infrakit/template/template.go | 47 +++++++++++++++++++------------ 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/cmd/infrakit/template/template.go b/cmd/infrakit/template/template.go index 64ef8e94c..b18cb53b6 100644 --- a/cmd/infrakit/template/template.go +++ b/cmd/infrakit/template/template.go @@ -29,32 +29,39 @@ func Command(plugins func() discovery.Plugins) *cobra.Command { cmd := &cobra.Command{ Use: "template ", Short: "Render an infrakit template at given url. If url is '-', read from stdin", - RunE: func(cmd *cobra.Command, args []string) error { + } - if len(args) != 1 { - cmd.Usage() - os.Exit(1) - } + outputFile := cmd.PersistentFlags().StringP("output", "o", "", "Output filename") + cmd.Flags().AddFlagSet(templateFlags) - url := args[0] - if url == "-" { - buff, err := ioutil.ReadAll(os.Stdin) - if err != nil { - return err - } - url = fmt.Sprintf("str://%s", string(buff)) - } + cmd.RunE = func(cmd *cobra.Command, args []string) error { - view, err := processTemplate(url) + if len(args) != 1 { + cmd.Usage() + os.Exit(1) + } + + url := args[0] + if url == "-" { + buff, err := ioutil.ReadAll(os.Stdin) if err != nil { return err } + url = fmt.Sprintf("str://%s", string(buff)) + } - fmt.Print(view) - return nil - }, + view, err := processTemplate(url) + if err != nil { + return err + } + + if *outputFile != "" { + return ioutil.WriteFile(*outputFile, []byte(view), 0644) + } + + fmt.Print(view) + return nil } - cmd.Flags().AddFlagSet(templateFlags) format := &cobra.Command{ Use: "format json|yaml", @@ -86,6 +93,10 @@ func Command(plugins func() discovery.Plugins) *cobra.Command { return err } + if *outputFile != "" { + return ioutil.WriteFile(*outputFile, buff, 0644) + } + fmt.Print(string(buff)) return nil From 6d88ade3335423c29c9e4b47deaefbe3aca6daec Mon Sep 17 00:00:00 2001 From: David Chung Date: Thu, 4 May 2017 16:40:44 -0700 Subject: [PATCH 2/3] vendor / new version of sprig Signed-off-by: David Chung --- vendor.conf | 2 +- .../github.com/Masterminds/sprig/.gitignore | 1 + .../github.com/Masterminds/sprig/.travis.yml | 9 +- vendor/github.com/Masterminds/sprig/Makefile | 13 + vendor/github.com/Masterminds/sprig/README.md | 91 ++- vendor/github.com/Masterminds/sprig/crypto.go | 148 ++++ vendor/github.com/Masterminds/sprig/date.go | 53 ++ .../github.com/Masterminds/sprig/defaults.go | 62 ++ vendor/github.com/Masterminds/sprig/dict.go | 74 ++ vendor/github.com/Masterminds/sprig/doc.go | 225 ++++++ .../github.com/Masterminds/sprig/functions.go | 733 ++---------------- .../github.com/Masterminds/sprig/glide.lock | 25 +- .../github.com/Masterminds/sprig/glide.yaml | 5 + vendor/github.com/Masterminds/sprig/list.go | 109 +++ .../github.com/Masterminds/sprig/numeric.go | 129 +++ .../github.com/Masterminds/sprig/reflect.go | 28 + vendor/github.com/Masterminds/sprig/semver.go | 23 + .../github.com/Masterminds/sprig/strings.go | 197 +++++ 18 files changed, 1222 insertions(+), 705 deletions(-) create mode 100644 vendor/github.com/Masterminds/sprig/Makefile create mode 100644 vendor/github.com/Masterminds/sprig/crypto.go create mode 100644 vendor/github.com/Masterminds/sprig/date.go create mode 100644 vendor/github.com/Masterminds/sprig/defaults.go create mode 100644 vendor/github.com/Masterminds/sprig/dict.go create mode 100644 vendor/github.com/Masterminds/sprig/doc.go create mode 100644 vendor/github.com/Masterminds/sprig/list.go create mode 100644 vendor/github.com/Masterminds/sprig/numeric.go create mode 100644 vendor/github.com/Masterminds/sprig/reflect.go create mode 100644 vendor/github.com/Masterminds/sprig/semver.go create mode 100644 vendor/github.com/Masterminds/sprig/strings.go diff --git a/vendor.conf b/vendor.conf index 824119492..5de32c5b4 100644 --- a/vendor.conf +++ b/vendor.conf @@ -7,7 +7,7 @@ # package github.com/docker/infrakit -github.com/Masterminds/sprig 2.8.0 +github.com/Masterminds/sprig 2.10.0 github.com/Microsoft/go-winio 0.3.6 github.com/Sirupsen/logrus v0.11.0-5-gabc6f20 github.com/aokoli/goutils 1.0.0 diff --git a/vendor/github.com/Masterminds/sprig/.gitignore b/vendor/github.com/Masterminds/sprig/.gitignore index 48b8bf907..5e3002f88 100644 --- a/vendor/github.com/Masterminds/sprig/.gitignore +++ b/vendor/github.com/Masterminds/sprig/.gitignore @@ -1 +1,2 @@ vendor/ +/.glide diff --git a/vendor/github.com/Masterminds/sprig/.travis.yml b/vendor/github.com/Masterminds/sprig/.travis.yml index 3caed53fa..933b295d0 100644 --- a/vendor/github.com/Masterminds/sprig/.travis.yml +++ b/vendor/github.com/Masterminds/sprig/.travis.yml @@ -1,9 +1,9 @@ language: go go: - - 1.3 - - 1.4 - - 1.5 + - 1.6 + - 1.7 + - 1.8 - tip # Setting sudo access to false will let Travis CI use containers rather than @@ -12,6 +12,9 @@ go: # - http://docs.travis-ci.com/user/workers/standard-infrastructure/ sudo: false +script: + - make setup test + notifications: webhooks: urls: diff --git a/vendor/github.com/Masterminds/sprig/Makefile b/vendor/github.com/Masterminds/sprig/Makefile new file mode 100644 index 000000000..63a93fdf7 --- /dev/null +++ b/vendor/github.com/Masterminds/sprig/Makefile @@ -0,0 +1,13 @@ + +HAS_GLIDE := $(shell command -v glide;) + +.PHONY: test +test: + go test -v . + +.PHONY: setup +setup: +ifndef HAS_GLIDE + go get -u github.com/Masterminds/glide +endif + glide install diff --git a/vendor/github.com/Masterminds/sprig/README.md b/vendor/github.com/Masterminds/sprig/README.md index a80835f98..097f1c1b6 100644 --- a/vendor/github.com/Masterminds/sprig/README.md +++ b/vendor/github.com/Masterminds/sprig/README.md @@ -108,6 +108,7 @@ parse, it returns the time unaltered. See `time.ParseDuration` for info on durat "one anchovy" "many anchovies"` - uuidv4: Generate a UUID v4 string - sha256sum: Generate a hex encoded sha256 hash of the input +- toString: Convert something to a string ### String Slice Functions: @@ -115,6 +116,10 @@ parse, it returns the time unaltered. See `time.ParseDuration` for info on durat - split: strings.Split, but as `split SEP STRING`. The results are returned as a map with the indexes set to _N, where N is an integer starting from 0. Use it like this: `{{$v := "foo/bar/baz" | split "/"}}{{$v._0}}` (Prints `foo`) +- splitList: strings.Split, but as `split SEP STRING`. The results are returned + as an array. +- toStrings: convert a list to a list of strings. 'list 1 2 3 | toStrings' produces '["1" "2" "3"]' +- sortAlpha: sort a list lexicographically. ### Integer Slice Functions: @@ -141,12 +146,26 @@ parse, it returns the time unaltered. See `time.ParseDuration` for info on durat no clear empty condition). For everything else, nil value triggers a default. - empty: Returns true if the given value is the zero value for that type. Structs are always non-empty. +- coalesce: Given a list of items, return the first non-empty one. + This follows the same rules as 'empty'. `{{ coalesce .someVal 0 "hello" }}` + will return `.someVal` if set, or else return "hello". The 0 is skipped + because it is an empty value. +- compact: Return a copy of a list with all of the empty values removed. + `list 0 1 2 "" | compact` will return `[1 2]` ### OS: - env: Read an environment variable. - expandenv: Expand all environment variables in a string. +### File Paths: +- base: Return the last element of a path. https://golang.org/pkg/path#Base +- dir: Remove the last element of a path. https://golang.org/pkg/path#Dir +- clean: Clean a path to the shortest equivalent name. (e.g. remove "foo/.." + from "foo/../bar.html") https://golang.org/pkg/path#Clean +- ext: Get the extension for a file path: https://golang.org/pkg/path#Ext +- isAbs: Returns true if a path is absolute: https://golang.org/pkg/path#IsAbs + ### Encoding: - b32enc: Encode a string into a Base32 string @@ -156,8 +175,11 @@ parse, it returns the time unaltered. See `time.ParseDuration` for info on durat ### Data Structures: -- tuple: A sequence of related objects. It is implemented as a - `[]interface{}`, where each item can be accessed using `index`. +- tuple: Takes an arbitrary list of items and returns a slice of items. Its + tuple-ish properties are mainly gained through the template idiom, and not + through an API provided here. WARNING: The implementation of tuple will + change in the future. +- list: An arbitrary ordered list of items. (This is prefered over tuple.) - dict: Takes a list of name/values and returns a map[string]interface{}. The first parameter is converted to a string and stored as a key, the second parameter is treated as the value. And so on, with odds as keys and @@ -165,7 +187,43 @@ parse, it returns the time unaltered. See `time.ParseDuration` for info on durat be assigned the empty string. Non-string keys are converted to strings as follows: []byte are converted, fmt.Stringers will have String() called. errors will have Error() called. All others will be passed through - fmt.Sprtinf("%v"). + fmt.Sprtinf("%v"). _dicts are unordered_. + +List: + +``` +{{$t := list 1 "a" "foo"}} +{{index $t 2}}{{index $t 0 }}{{index $t 1}} +{{/* Prints foo1a *}} +``` + +Dict: +``` +{{ $t := map "key1" "value1" "key2" "value2" }} +{{ $t.key2 }} +{{ /* Prints value2 *}} +``` + + +### Lists Functions: + +These are used to manipulate lists: `{{ list 1 2 3 | reverse | first }}` + +- first: Get the first item in a 'list'. 'list 1 2 3 | first' prints '1' +- last: Get the last item in a 'list': 'list 1 2 3 | last ' prints '3' +- rest: Get all but the first item in a list: 'list 1 2 3 | rest' returns '[2 3]' +- initial: Get all but the last item in a list: 'list 1 2 3 | initial' returns '[1 2]' +- append: Add an item to the end of a list: 'append $list 4' adds '4' to the end of '$list' +- prepend: Add an item to the beginning of a list: 'prepend $list 4' puts 4 at the beginning of the list. +- reverse: Reverse the items in a list. +- uniq: Remove duplicates from a list. +- without: Return a list with the given values removed: 'without (list 1 2 3) 1' would return '[2 3]' +- has: Return 'tru' if the item is found in the list: 'has "foo" $list' will return 'true' if the list contains "foo" + +### Dict Functions: + +These are used to manipulate dicts. + - set: Takes a dict, a key, and a value, and sets that key/value pair in the dict. `set $dict $key $value`. For convenience, it returns the dict, even though the dict was modified in place. @@ -173,12 +231,10 @@ parse, it returns the time unaltered. See `time.ParseDuration` for info on durat dict. `unset $dict $key`. This returns the dict for convenience. - hasKey: Takes a dict and a key, and returns boolean true if the key is in the dict. - -``` -{{$t := tuple 1 "a" "foo"}} -{{index $t 2}}{{index $t 0 }}{{index $t 1}} -{{/* Prints foo1a *}} -``` +- pluck: Given a key and one or more maps, get all of the values for that key. +- keys: Get an array of all of the keys in a dict. Order is not guaranteed. +- pick: Select just the given keys out of the dict, and return a new dict. +- omit: Return a dict without the given keys. ### Reflection: @@ -215,6 +271,23 @@ string is passed in, functions will attempt to conver with - min: Return the smallest of a series of integers. `min 1 2 3` returns `1`. +### Cryptographic Functions: + +- derivePassword: Derive a password from the given parameters according to the "Master Password" algorithm (http://masterpasswordapp.com/algorithm.html) + Given parameters (in order) are: + `counter` (starting with 1), `password_type` (maximum, long, medium, short, basic, or pin), `password`, + `user`, and `site`. The following line generates a long password for the user "user" and with a master-password "password" on the site "example.com": + ``` + {{ derivePassword 1 "long" "password" "user" "example.com" }} + ``` + +## SemVer Functions: + +These functions provide version parsing and comparisons for SemVer 2 version +strings. + +- semver: Parse a semantic version and return a Version object. +- semverCompare: Compare a SemVer range to a particular version. ## Principles: diff --git a/vendor/github.com/Masterminds/sprig/crypto.go b/vendor/github.com/Masterminds/sprig/crypto.go new file mode 100644 index 000000000..a935b6c1a --- /dev/null +++ b/vendor/github.com/Masterminds/sprig/crypto.go @@ -0,0 +1,148 @@ +package sprig + +import ( + "bytes" + "crypto/dsa" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/hmac" + "crypto/rand" + "crypto/rsa" + "crypto/sha256" + "crypto/x509" + "encoding/asn1" + "encoding/binary" + "encoding/hex" + "encoding/pem" + "fmt" + "math/big" + + uuid "github.com/satori/go.uuid" + "golang.org/x/crypto/scrypt" +) + +func sha256sum(input string) string { + hash := sha256.Sum256([]byte(input)) + return hex.EncodeToString(hash[:]) +} + +// uuidv4 provides a safe and secure UUID v4 implementation +func uuidv4() string { + return fmt.Sprintf("%s", uuid.NewV4()) +} + +var master_password_seed = "com.lyndir.masterpassword" + +var password_type_templates = map[string][][]byte{ + "maximum": {[]byte("anoxxxxxxxxxxxxxxxxx"), []byte("axxxxxxxxxxxxxxxxxno")}, + "long": {[]byte("CvcvnoCvcvCvcv"), []byte("CvcvCvcvnoCvcv"), []byte("CvcvCvcvCvcvno"), []byte("CvccnoCvcvCvcv"), []byte("CvccCvcvnoCvcv"), + []byte("CvccCvcvCvcvno"), []byte("CvcvnoCvccCvcv"), []byte("CvcvCvccnoCvcv"), []byte("CvcvCvccCvcvno"), []byte("CvcvnoCvcvCvcc"), + []byte("CvcvCvcvnoCvcc"), []byte("CvcvCvcvCvccno"), []byte("CvccnoCvccCvcv"), []byte("CvccCvccnoCvcv"), []byte("CvccCvccCvcvno"), + []byte("CvcvnoCvccCvcc"), []byte("CvcvCvccnoCvcc"), []byte("CvcvCvccCvccno"), []byte("CvccnoCvcvCvcc"), []byte("CvccCvcvnoCvcc"), + []byte("CvccCvcvCvccno")}, + "medium": {[]byte("CvcnoCvc"), []byte("CvcCvcno")}, + "short": {[]byte("Cvcn")}, + "basic": {[]byte("aaanaaan"), []byte("aannaaan"), []byte("aaannaaa")}, + "pin": {[]byte("nnnn")}, +} + +var template_characters = map[byte]string{ + 'V': "AEIOU", + 'C': "BCDFGHJKLMNPQRSTVWXYZ", + 'v': "aeiou", + 'c': "bcdfghjklmnpqrstvwxyz", + 'A': "AEIOUBCDFGHJKLMNPQRSTVWXYZ", + 'a': "AEIOUaeiouBCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz", + 'n': "0123456789", + 'o': "@&%?,=[]_:-+*$#!'^~;()/.", + 'x': "AEIOUaeiouBCDFGHJKLMNPQRSTVWXYZbcdfghjklmnpqrstvwxyz0123456789!@#$%^&*()", +} + +func derivePassword(counter uint32, password_type, password, user, site string) string { + var templates = password_type_templates[password_type] + if templates == nil { + return fmt.Sprintf("cannot find password template %s", password_type) + } + + var buffer bytes.Buffer + buffer.WriteString(master_password_seed) + binary.Write(&buffer, binary.BigEndian, uint32(len(user))) + buffer.WriteString(user) + + salt := buffer.Bytes() + key, err := scrypt.Key([]byte(password), salt, 32768, 8, 2, 64) + if err != nil { + return fmt.Sprintf("failed to derive password: %s", err) + } + + buffer.Truncate(len(master_password_seed)) + binary.Write(&buffer, binary.BigEndian, uint32(len(site))) + buffer.WriteString(site) + binary.Write(&buffer, binary.BigEndian, counter) + + var hmacv = hmac.New(sha256.New, key) + hmacv.Write(buffer.Bytes()) + var seed = hmacv.Sum(nil) + var temp = templates[int(seed[0])%len(templates)] + + buffer.Truncate(0) + for i, element := range temp { + pass_chars := template_characters[element] + pass_char := pass_chars[int(seed[i+1])%len(pass_chars)] + buffer.WriteByte(pass_char) + } + + return buffer.String() +} + +func generatePrivateKey(typ string) string { + var priv interface{} + var err error + switch typ { + case "", "rsa": + // good enough for government work + priv, err = rsa.GenerateKey(rand.Reader, 4096) + case "dsa": + key := new(dsa.PrivateKey) + // again, good enough for government work + if err = dsa.GenerateParameters(&key.Parameters, rand.Reader, dsa.L2048N256); err != nil { + return fmt.Sprintf("failed to generate dsa params: %s", err) + } + err = dsa.GenerateKey(key, rand.Reader) + priv = key + case "ecdsa": + // again, good enough for government work + priv, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + default: + return "Unknown type " + typ + } + if err != nil { + return fmt.Sprintf("failed to generate private key: %s", err) + } + + return string(pem.EncodeToMemory(pemBlockForKey(priv))) +} + +type DSAKeyFormat struct { + Version int + P, Q, G, Y, X *big.Int +} + +func pemBlockForKey(priv interface{}) *pem.Block { + switch k := priv.(type) { + case *rsa.PrivateKey: + return &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)} + case *dsa.PrivateKey: + val := DSAKeyFormat{ + P: k.P, Q: k.Q, G: k.G, + Y: k.Y, X: k.X, + } + bytes, _ := asn1.Marshal(val) + return &pem.Block{Type: "DSA PRIVATE KEY", Bytes: bytes} + case *ecdsa.PrivateKey: + b, _ := x509.MarshalECPrivateKey(k) + return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b} + default: + return nil + } +} diff --git a/vendor/github.com/Masterminds/sprig/date.go b/vendor/github.com/Masterminds/sprig/date.go new file mode 100644 index 000000000..dc5263f24 --- /dev/null +++ b/vendor/github.com/Masterminds/sprig/date.go @@ -0,0 +1,53 @@ +package sprig + +import ( + "time" +) + +// Given a format and a date, format the date string. +// +// Date can be a `time.Time` or an `int, int32, int64`. +// In the later case, it is treated as seconds since UNIX +// epoch. +func date(fmt string, date interface{}) string { + return dateInZone(fmt, date, "Local") +} + +func htmlDate(date interface{}) string { + return dateInZone("2006-01-02", date, "Local") +} + +func htmlDateInZone(date interface{}, zone string) string { + return dateInZone("2006-01-02", date, zone) +} + +func dateInZone(fmt string, date interface{}, zone string) string { + var t time.Time + switch date := date.(type) { + default: + t = time.Now() + case time.Time: + t = date + case int64: + t = time.Unix(date, 0) + case int: + t = time.Unix(int64(date), 0) + case int32: + t = time.Unix(int64(date), 0) + } + + loc, err := time.LoadLocation(zone) + if err != nil { + loc, _ = time.LoadLocation("UTC") + } + + return t.In(loc).Format(fmt) +} + +func dateModify(fmt string, date time.Time) time.Time { + d, err := time.ParseDuration(fmt) + if err != nil { + return date + } + return date.Add(d) +} diff --git a/vendor/github.com/Masterminds/sprig/defaults.go b/vendor/github.com/Masterminds/sprig/defaults.go new file mode 100644 index 000000000..9892f07ed --- /dev/null +++ b/vendor/github.com/Masterminds/sprig/defaults.go @@ -0,0 +1,62 @@ +package sprig + +import ( + "reflect" +) + +// dfault checks whether `given` is set, and returns default if not set. +// +// This returns `d` if `given` appears not to be set, and `given` otherwise. +// +// For numeric types 0 is unset. +// For strings, maps, arrays, and slices, len() = 0 is considered unset. +// For bool, false is unset. +// Structs are never considered unset. +// +// For everything else, including pointers, a nil value is unset. +func dfault(d interface{}, given ...interface{}) interface{} { + + if empty(given) || empty(given[0]) { + return d + } + return given[0] +} + +// empty returns true if the given value has the zero value for its type. +func empty(given interface{}) bool { + g := reflect.ValueOf(given) + if !g.IsValid() { + return true + } + + // Basically adapted from text/template.isTrue + switch g.Kind() { + default: + return g.IsNil() + case reflect.Array, reflect.Slice, reflect.Map, reflect.String: + return g.Len() == 0 + case reflect.Bool: + return g.Bool() == false + case reflect.Complex64, reflect.Complex128: + return g.Complex() == 0 + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return g.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return g.Uint() == 0 + case reflect.Float32, reflect.Float64: + return g.Float() == 0 + case reflect.Struct: + return false + } + return true +} + +// coalesce returns the first non-empty value. +func coalesce(v ...interface{}) interface{} { + for _, val := range v { + if !empty(val) { + return val + } + } + return nil +} diff --git a/vendor/github.com/Masterminds/sprig/dict.go b/vendor/github.com/Masterminds/sprig/dict.go new file mode 100644 index 000000000..d50e11c5c --- /dev/null +++ b/vendor/github.com/Masterminds/sprig/dict.go @@ -0,0 +1,74 @@ +package sprig + +func set(d map[string]interface{}, key string, value interface{}) map[string]interface{} { + d[key] = value + return d +} + +func unset(d map[string]interface{}, key string) map[string]interface{} { + delete(d, key) + return d +} + +func hasKey(d map[string]interface{}, key string) bool { + _, ok := d[key] + return ok +} + +func pluck(key string, d ...map[string]interface{}) []interface{} { + res := []interface{}{} + for _, dict := range d { + if val, ok := dict[key]; ok { + res = append(res, val) + } + } + return res +} + +func keys(dict map[string]interface{}) []string { + k := []string{} + for key := range dict { + k = append(k, key) + } + return k +} + +func pick(dict map[string]interface{}, keys ...string) map[string]interface{} { + res := map[string]interface{}{} + for _, k := range keys { + if v, ok := dict[k]; ok { + res[k] = v + } + } + return res +} + +func omit(dict map[string]interface{}, keys ...string) map[string]interface{} { + res := map[string]interface{}{} + + omit := make(map[string]bool, len(keys)) + for _, k := range keys { + omit[k] = true + } + + for k, v := range dict { + if _, ok := omit[k]; !ok { + res[k] = v + } + } + return res +} + +func dict(v ...interface{}) map[string]interface{} { + dict := map[string]interface{}{} + lenv := len(v) + for i := 0; i < lenv; i += 2 { + key := strval(v[i]) + if i+1 >= lenv { + dict[key] = "" + continue + } + dict[key] = v[i+1] + } + return dict +} diff --git a/vendor/github.com/Masterminds/sprig/doc.go b/vendor/github.com/Masterminds/sprig/doc.go new file mode 100644 index 000000000..7ecb5e25d --- /dev/null +++ b/vendor/github.com/Masterminds/sprig/doc.go @@ -0,0 +1,225 @@ +/* +Sprig: Template functions for Go. + +This package contains a number of utility functions for working with data +inside of Go `html/template` and `text/template` files. + +To add these functions, use the `template.Funcs()` method: + + t := templates.New("foo").Funcs(sprig.FuncMap()) + +Note that you should add the function map before you parse any template files. + + In several cases, Sprig reverses the order of arguments from the way they + appear in the standard library. This is to make it easier to pipe + arguments into functions. + +Date Functions + + - date FORMAT TIME: Format a date, where a date is an integer type or a time.Time type, and + format is a time.Format formatting string. + - dateModify: Given a date, modify it with a duration: `date_modify "-1.5h" now`. If the duration doesn't + parse, it returns the time unaltered. See `time.ParseDuration` for info on duration strings. + - now: Current time.Time, for feeding into date-related functions. + - htmlDate TIME: Format a date for use in the value field of an HTML "date" form element. + - dateInZone FORMAT TIME TZ: Like date, but takes three arguments: format, timestamp, + timezone. + - htmlDateInZone TIME TZ: Like htmlDate, but takes two arguments: timestamp, + timezone. + +String Functions + + - abbrev: Truncate a string with ellipses. `abbrev 5 "hello world"` yields "he..." + - abbrevboth: Abbreviate from both sides, yielding "...lo wo..." + - trunc: Truncate a string (no suffix). `trunc 5 "Hello World"` yields "hello". + - trim: strings.TrimSpace + - trimAll: strings.Trim, but with the argument order reversed `trimAll "$" "$5.00"` or `"$5.00 | trimAll "$"` + - trimSuffix: strings.TrimSuffix, but with the argument order reversed: `trimSuffix "-" "ends-with-"` + - trimPrefix: strings.TrimPrefix, but with the argument order reversed `trimPrefix "$" "$5"` + - upper: strings.ToUpper + - lower: strings.ToLower + - nospace: Remove all space characters from a string. `nospace "h e l l o"` becomes "hello" + - title: strings.Title + - untitle: Remove title casing + - repeat: strings.Repeat, but with the arguments switched: `repeat count str`. (This simplifies common pipelines) + - substr: Given string, start, and length, return a substr. + - initials: Given a multi-word string, return the initials. `initials "Matt Butcher"` returns "MB" + - randAlphaNum: Given a length, generate a random alphanumeric sequence + - randAlpha: Given a length, generate an alphabetic string + - randAscii: Given a length, generate a random ASCII string (symbols included) + - randNumeric: Given a length, generate a string of digits. + - wrap: Force a line wrap at the given width. `wrap 80 "imagine a longer string"` + - wrapWith: Wrap a line at the given length, but using 'sep' instead of a newline. `wrapWith 50, "
", $html` + - contains: strings.Contains, but with the arguments switched: `contains substr str`. (This simplifies common pipelines) + - hasPrefix: strings.hasPrefix, but with the arguments switched + - hasSuffix: strings.hasSuffix, but with the arguments switched + - quote: Wrap string(s) in double quotation marks, escape the contents by adding '\' before '"'. + - squote: Wrap string(s) in double quotation marks, does not escape content. + - cat: Concatenate strings, separating them by spaces. `cat $a $b $c`. + - indent: Indent a string using space characters. `indent 4 "foo\nbar"` produces " foo\n bar" + - replace: Replace an old with a new in a string: `$name | replace " " "-"` + - plural: Choose singular or plural based on length: `len $fish | plural "one anchovy" "many anchovies"` + - sha256sum: Generate a hex encoded sha256 hash of the input + - toString: Convert something to a string + +String Slice Functions: + + - join: strings.Join, but as `join SEP SLICE` + - split: strings.Split, but as `split SEP STRING`. The results are returned + as a map with the indexes set to _N, where N is an integer starting from 0. + Use it like this: `{{$v := "foo/bar/baz" | split "/"}}{{$v._0}}` (Prints `foo`) + - splitList: strings.Split, but as `split SEP STRING`. The results are returned + as an array. + - toStrings: convert a list to a list of strings. 'list 1 2 3 | toStrings' produces '["1" "2" "3"]' + - sortAlpha: sort a list lexicographically. + +Integer Slice Functions: + + - until: Given an integer, returns a slice of counting integers from 0 to one + less than the given integer: `range $i, $e := until 5` + - untilStep: Given start, stop, and step, return an integer slice starting at + 'start', stopping at `stop`, and incrementing by 'step. This is the same + as Python's long-form of 'range'. + +Conversions: + + - atoi: Convert a string to an integer. 0 if the integer could not be parsed. + - in64: Convert a string or another numeric type to an int64. + - int: Convert a string or another numeric type to an int. + - float64: Convert a string or another numeric type to a float64. + +Defaults: + + - default: Give a default value. Used like this: trim " "| default "empty". + Since trim produces an empty string, the default value is returned. For + things with a length (strings, slices, maps), len(0) will trigger the default. + For numbers, the value 0 will trigger the default. For booleans, false will + trigger the default. For structs, the default is never returned (there is + no clear empty condition). For everything else, nil value triggers a default. + - empty: Return true if the given value is the zero value for its type. + Caveats: structs are always non-empty. This should match the behavior of + {{if pipeline}}, but can be used inside of a pipeline. + - coalesce: Given a list of items, return the first non-empty one. + This follows the same rules as 'empty'. '{{ coalesce .someVal 0 "hello" }}` + will return `.someVal` if set, or else return "hello". The 0 is skipped + because it is an empty value. + - compact: Return a copy of a list with all of the empty values removed. + 'list 0 1 2 "" | compact' will return '[1 2]' + +OS: + - env: Resolve an environment variable + - expandenv: Expand a string through the environment + +File Paths: + - base: Return the last element of a path. https://golang.org/pkg/path#Base + - dir: Remove the last element of a path. https://golang.org/pkg/path#Dir + - clean: Clean a path to the shortest equivalent name. (e.g. remove "foo/.." + from "foo/../bar.html") https://golang.org/pkg/path#Clean + - ext: https://golang.org/pkg/path#Ext + - isAbs: https://golang.org/pkg/path#IsAbs + +Encoding: + - b64enc: Base 64 encode a string. + - b64dec: Base 64 decode a string. + +Reflection: + + - typeOf: Takes an interface and returns a string representation of the type. + For pointers, this will return a type prefixed with an asterisk(`*`). So + a pointer to type `Foo` will be `*Foo`. + - typeIs: Compares an interface with a string name, and returns true if they match. + Note that a pointer will not match a reference. For example `*Foo` will not + match `Foo`. + - typeIsLike: Compares an interface with a string name and returns true if + the interface is that `name` or that `*name`. In other words, if the given + value matches the given type or is a pointer to the given type, this returns + true. + - kindOf: Takes an interface and returns a string representation of its kind. + - kindIs: Returns true if the given string matches the kind of the given interface. + + Note: None of these can test whether or not something implements a given + interface, since doing so would require compiling the interface in ahead of + time. + +Data Structures: + + - tuple: Takes an arbitrary list of items and returns a slice of items. Its + tuple-ish properties are mainly gained through the template idiom, and not + through an API provided here. WARNING: The implementation of tuple will + change in the future. + - list: An arbitrary ordered list of items. (This is prefered over tuple.) + - dict: Takes a list of name/values and returns a map[string]interface{}. + The first parameter is converted to a string and stored as a key, the + second parameter is treated as the value. And so on, with odds as keys and + evens as values. If the function call ends with an odd, the last key will + be assigned the empty string. Non-string keys are converted to strings as + follows: []byte are converted, fmt.Stringers will have String() called. + errors will have Error() called. All others will be passed through + fmt.Sprtinf("%v"). + +Lists Functions: + +These are used to manipulate lists: '{{ list 1 2 3 | reverse | first }}' + + - first: Get the first item in a 'list'. 'list 1 2 3 | first' prints '1' + - last: Get the last item in a 'list': 'list 1 2 3 | last ' prints '3' + - rest: Get all but the first item in a list: 'list 1 2 3 | rest' returns '[2 3]' + - initial: Get all but the last item in a list: 'list 1 2 3 | initial' returns '[1 2]' + - append: Add an item to the end of a list: 'append $list 4' adds '4' to the end of '$list' + - prepend: Add an item to the beginning of a list: 'prepend $list 4' puts 4 at the beginning of the list. + - reverse: Reverse the items in a list. + - uniq: Remove duplicates from a list. + - without: Return a list with the given values removed: 'without (list 1 2 3) 1' would return '[2 3]' + - has: Return 'true' if the item is found in the list: 'has "foo" $list' will return 'true' if the list contains "foo" + +Dict Functions: + +These are used to manipulate dicts. + + - set: Takes a dict, a key, and a value, and sets that key/value pair in + the dict. `set $dict $key $value`. For convenience, it returns the dict, + even though the dict was modified in place. + - unset: Takes a dict and a key, and deletes that key/value pair from the + dict. `unset $dict $key`. This returns the dict for convenience. + - hasKey: Takes a dict and a key, and returns boolean true if the key is in + the dict. + - pluck: Given a key and one or more maps, get all of the values for that key. + - keys: Get an array of all of the keys in a dict. + - pick: Select just the given keys out of the dict, and return a new dict. + - omit: Return a dict without the given keys. + +Math Functions: + +Integer functions will convert integers of any width to `int64`. If a +string is passed in, functions will attempt to convert with +`strconv.ParseInt(s, 1064)`. If this fails, the value will be treated as 0. + + - add1: Increment an integer by 1 + - add: Sum an arbitrary number of integers + - sub: Subtract the second integer from the first + - div: Divide the first integer by the second + - mod: Module of first integer divided by second + - mul: Multiply integers + - max: Return the biggest of a series of one or more integers + - min: Return the smallest of a series of one or more integers + - biggest: DEPRECATED. Return the biggest of a series of one or more integers + +Crypto Functions: + + - genPrivateKey: Generate a private key for the given cryptosystem. If no + argument is supplied, by default it will generate a private key using + the RSA algorithm. Accepted values are `rsa`, `dsa`, and `ecdsa`. + - derivePassword: Derive a password from the given parameters according to the ["Master Password" algorithm](http://masterpasswordapp.com/algorithm.html) + Given parameters (in order) are: + `counter` (starting with 1), `password_type` (maximum, long, medium, short, basic, or pin), `password`, + `user`, and `site` + +SemVer Functions: + +These functions provide version parsing and comparisons for SemVer 2 version +strings. + + - semver: Parse a semantic version and return a Version object. + - semverCompare: Compare a SemVer range to a particular version. +*/ +package sprig diff --git a/vendor/github.com/Masterminds/sprig/functions.go b/vendor/github.com/Masterminds/sprig/functions.go index 601f3b580..be108360f 100644 --- a/vendor/github.com/Masterminds/sprig/functions.go +++ b/vendor/github.com/Masterminds/sprig/functions.go @@ -1,215 +1,22 @@ -/* -Sprig: Template functions for Go. - -This package contains a number of utility functions for working with data -inside of Go `html/template` and `text/template` files. - -To add these functions, use the `template.Funcs()` method: - - t := templates.New("foo").Funcs(sprig.FuncMap()) - -Note that you should add the function map before you parse any template files. - - In several cases, Sprig reverses the order of arguments from the way they - appear in the standard library. This is to make it easier to pipe - arguments into functions. - -Date Functions - - - date FORMAT TIME: Format a date, where a date is an integer type or a time.Time type, and - format is a time.Format formatting string. - - dateModify: Given a date, modify it with a duration: `date_modify "-1.5h" now`. If the duration doesn't - parse, it returns the time unaltered. See `time.ParseDuration` for info on duration strings. - - now: Current time.Time, for feeding into date-related functions. - - htmlDate TIME: Format a date for use in the value field of an HTML "date" form element. - - dateInZone FORMAT TIME TZ: Like date, but takes three arguments: format, timestamp, - timezone. - - htmlDateInZone TIME TZ: Like htmlDate, but takes two arguments: timestamp, - timezone. - -String Functions - - - abbrev: Truncate a string with ellipses. `abbrev 5 "hello world"` yields "he..." - - abbrevboth: Abbreviate from both sides, yielding "...lo wo..." - - trunc: Truncate a string (no suffix). `trunc 5 "Hello World"` yields "hello". - - trim: strings.TrimSpace - - trimAll: strings.Trim, but with the argument order reversed `trimAll "$" "$5.00"` or `"$5.00 | trimAll "$"` - - trimSuffix: strings.TrimSuffix, but with the argument order reversed: `trimSuffix "-" "ends-with-"` - - trimPrefix: strings.TrimPrefix, but with the argument order reversed `trimPrefix "$" "$5"` - - upper: strings.ToUpper - - lower: strings.ToLower - - nospace: Remove all space characters from a string. `nospace "h e l l o"` becomes "hello" - - title: strings.Title - - untitle: Remove title casing - - repeat: strings.Repeat, but with the arguments switched: `repeat count str`. (This simplifies common pipelines) - - substr: Given string, start, and length, return a substr. - - initials: Given a multi-word string, return the initials. `initials "Matt Butcher"` returns "MB" - - randAlphaNum: Given a length, generate a random alphanumeric sequence - - randAlpha: Given a length, generate an alphabetic string - - randAscii: Given a length, generate a random ASCII string (symbols included) - - randNumeric: Given a length, generate a string of digits. - - wrap: Force a line wrap at the given width. `wrap 80 "imagine a longer string"` - - wrapWith: Wrap a line at the given length, but using 'sep' instead of a newline. `wrapWith 50, "
", $html` - - contains: strings.Contains, but with the arguments switched: `contains substr str`. (This simplifies common pipelines) - - hasPrefix: strings.hasPrefix, but with the arguments switched - - hasSuffix: strings.hasSuffix, but with the arguments switched - - quote: Wrap string(s) in double quotation marks, escape the contents by adding '\' before '"'. - - squote: Wrap string(s) in double quotation marks, does not escape content. - - cat: Concatenate strings, separating them by spaces. `cat $a $b $c`. - - indent: Indent a string using space characters. `indent 4 "foo\nbar"` produces " foo\n bar" - - replace: Replace an old with a new in a string: `$name | replace " " "-"` - - plural: Choose singular or plural based on length: `len $fish | plural "one anchovy" "many anchovies"` - - sha256sum: Generate a hex encoded sha256 hash of the input - -String Slice Functions: - - - join: strings.Join, but as `join SEP SLICE` - - split: strings.Split, but as `split SEP STRING`. The results are returned - as a map with the indexes set to _N, where N is an integer starting from 0. - Use it like this: `{{$v := "foo/bar/baz" | split "/"}}{{$v._0}}` (Prints `foo`) - -Integer Slice Functions: - - - until: Given an integer, returns a slice of counting integers from 0 to one - less than the given integer: `range $i, $e := until 5` - - untilStep: Given start, stop, and step, return an integer slice starting at - 'start', stopping at `stop`, and incrementing by 'step. This is the same - as Python's long-form of 'range'. - -Conversions: - - - atoi: Convert a string to an integer. 0 if the integer could not be parsed. - - in64: Convert a string or another numeric type to an int64. - - int: Convert a string or another numeric type to an int. - - float64: Convert a string or another numeric type to a float64. - -Defaults: - - - default: Give a default value. Used like this: trim " "| default "empty". - Since trim produces an empty string, the default value is returned. For - things with a length (strings, slices, maps), len(0) will trigger the default. - For numbers, the value 0 will trigger the default. For booleans, false will - trigger the default. For structs, the default is never returned (there is - no clear empty condition). For everything else, nil value triggers a default. - - empty: Return true if the given value is the zero value for its type. - Caveats: structs are always non-empty. This should match the behavior of - {{if pipeline}}, but can be used inside of a pipeline. - -OS: - - env: Resolve an environment variable - - expandenv: Expand a string through the environment - -File Paths: - - base: Return the last element of a path. https://golang.org/pkg/path#Base - - dir: Remove the last element of a path. https://golang.org/pkg/path#Dir - - clean: Clean a path to the shortest equivalent name. (e.g. remove "foo/.." - from "foo/../bar.html") https://golang.org/pkg/path#Clean - - ext: https://golang.org/pkg/path#Ext - - isAbs: https://golang.org/pkg/path#IsAbs - -Encoding: - - b64enc: Base 64 encode a string. - - b64dec: Base 64 decode a string. - -Reflection: - - - typeOf: Takes an interface and returns a string representation of the type. - For pointers, this will return a type prefixed with an asterisk(`*`). So - a pointer to type `Foo` will be `*Foo`. - - typeIs: Compares an interface with a string name, and returns true if they match. - Note that a pointer will not match a reference. For example `*Foo` will not - match `Foo`. - - typeIsLike: Compares an interface with a string name and returns true if - the interface is that `name` or that `*name`. In other words, if the given - value matches the given type or is a pointer to the given type, this returns - true. - - kindOf: Takes an interface and returns a string representation of its kind. - - kindIs: Returns true if the given string matches the kind of the given interface. - - Note: None of these can test whether or not something implements a given - interface, since doing so would require compiling the interface in ahead of - time. - -Data Structures: - - - tuple: Takes an arbitrary list of items and returns a slice of items. Its - tuple-ish properties are mainly gained through the template idiom, and not - through an API provided here. - - dict: Takes a list of name/values and returns a map[string]interface{}. - The first parameter is converted to a string and stored as a key, the - second parameter is treated as the value. And so on, with odds as keys and - evens as values. If the function call ends with an odd, the last key will - be assigned the empty string. Non-string keys are converted to strings as - follows: []byte are converted, fmt.Stringers will have String() called. - errors will have Error() called. All others will be passed through - fmt.Sprtinf("%v"). - - set: Takes a dict, a key, and a value, and sets that key/value pair in - the dict. `set $dict $key $value`. For convenience, it returns the dict, - even though the dict was modified in place. - - unset: Takes a dict and a key, and deletes that key/value pair from the - dict. `unset $dict $key`. This returns the dict for convenience. - - hasKey: Takes a dict and a key, and returns boolean true if the key is in - the dict. - -Math Functions: - -Integer functions will convert integers of any width to `int64`. If a -string is passed in, functions will attempt to convert with -`strconv.ParseInt(s, 1064)`. If this fails, the value will be treated as 0. - - - add1: Increment an integer by 1 - - add: Sum an arbitrary number of integers - - sub: Subtract the second integer from the first - - div: Divide the first integer by the second - - mod: Module of first integer divided by second - - mul: Multiply integers - - max: Return the biggest of a series of one or more integers - - min: Return the smallest of a series of one or more integers - - biggest: DEPRECATED. Return the biggest of a series of one or more integers - -Crypto Functions: - - - genPrivateKey: Generate a private key for the given cryptosystem. If no - argument is supplied, by default it will generate a private key using - the RSA algorithm. Accepted values are `rsa`, `dsa`, and `ecdsa`. - -*/ package sprig import ( - "crypto/dsa" - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" - "crypto/rsa" - "crypto/sha256" - "crypto/x509" - "encoding/asn1" - "encoding/base32" - "encoding/base64" - "encoding/hex" - "encoding/pem" - "fmt" "html/template" - "math" - "math/big" "os" "path" - "reflect" "strconv" "strings" ttemplate "text/template" "time" util "github.com/aokoli/goutils" - uuid "github.com/satori/go.uuid" ) // Produce the function map. // // Use this to pass the functions into the template engine: // -// tpl := template.New("foo").Funcs(sprig.FuncMap)) +// tpl := template.New("foo").Funcs(sprig.FuncMap())) // func FuncMap() template.FuncMap { return HtmlFuncMap() @@ -235,12 +42,21 @@ func HermeticHtmlFuncMap() template.FuncMap { // TextFuncMap returns a 'text/template'.FuncMap func TxtFuncMap() ttemplate.FuncMap { - return ttemplate.FuncMap(genericMap) + return ttemplate.FuncMap(GenericFuncMap()) } // HtmlFuncMap returns an 'html/template'.Funcmap func HtmlFuncMap() template.FuncMap { - return template.FuncMap(genericMap) + return template.FuncMap(GenericFuncMap()) +} + +// GenericFuncMap returns a copy of the basic function map as a map[string]interface{}. +func GenericFuncMap() map[string]interface{} { + gfm := make(map[string]interface{}, len(genericMap)) + for k, v := range genericMap { + gfm[k] = v + } + return gfm } // These functions are not guaranteed to evaluate to the same result for given input, because they @@ -319,6 +135,7 @@ var genericMap = map[string]interface{}{ "replace": replace, "plural": plural, "sha256sum": sha256sum, + "toString": strval, // Wrap Atoi to stop errors. "atoi": func(a string) int { i, _ := strconv.Atoi(a); return i }, @@ -332,7 +149,9 @@ var genericMap = map[string]interface{}{ //"lte": func(a, b int) bool {return a <= b}, // split "/" foo/bar returns map[int]string{0: foo, 1: bar} - "split": split, + "split": split, + "splitList": func(sep, orig string) []string { return strings.Split(orig, sep) }, + "toStrings": strslice, "until": until, "untilStep": untilStep, @@ -362,11 +181,14 @@ var genericMap = map[string]interface{}{ // string slices. Note that we reverse the order b/c that's better // for template processing. - "join": func(sep string, ss []string) string { return strings.Join(ss, sep) }, + "join": join, + "sortAlpha": sortAlpha, // Defaults - "default": dfault, - "empty": empty, + "default": dfault, + "empty": empty, + "coalesce": coalesce, + "compact": compact, // Reflection "typeOf": typeOf, @@ -393,503 +215,36 @@ var genericMap = map[string]interface{}{ "b32dec": base32decode, // Data Structures: - "tuple": tuple, + "tuple": list, // FIXME: with the addition of append/prepend these are no longer immutable. + "list": list, "dict": dict, "set": set, "unset": unset, "hasKey": hasKey, + "pluck": pluck, + "keys": keys, + "pick": pick, + "omit": omit, + + "append": push, "push": push, + "prepend": prepend, + "first": first, + "rest": rest, + "last": last, + "initial": initial, + "reverse": reverse, + "uniq": uniq, + "without": without, + "has": func(needle interface{}, haystack []interface{}) bool { return inList(haystack, needle) }, // Crypto: - "genPrivateKey": generatePrivateKey, + "genPrivateKey": generatePrivateKey, + "derivePassword": derivePassword, // UUIDs: "uuidv4": uuidv4, -} - -func split(sep, orig string) map[string]string { - parts := strings.Split(orig, sep) - res := make(map[string]string, len(parts)) - for i, v := range parts { - res["_"+strconv.Itoa(i)] = v - } - return res -} - -// substring creates a substring of the given string. -// -// If start is < 0, this calls string[:length]. -// -// If start is >= 0 and length < 0, this calls string[start:] -// -// Otherwise, this calls string[start, length]. -func substring(start, length int, s string) string { - if start < 0 { - return s[:length] - } - if length < 0 { - return s[start:] - } - return s[start:length] -} - -// Given a format and a date, format the date string. -// -// Date can be a `time.Time` or an `int, int32, int64`. -// In the later case, it is treated as seconds since UNIX -// epoch. -func date(fmt string, date interface{}) string { - return dateInZone(fmt, date, "Local") -} - -func htmlDate(date interface{}) string { - return dateInZone("2006-01-02", date, "Local") -} - -func htmlDateInZone(date interface{}, zone string) string { - return dateInZone("2006-01-02", date, zone) -} - -func dateInZone(fmt string, date interface{}, zone string) string { - var t time.Time - switch date := date.(type) { - default: - t = time.Now() - case time.Time: - t = date - case int64: - t = time.Unix(date, 0) - case int: - t = time.Unix(int64(date), 0) - case int32: - t = time.Unix(int64(date), 0) - } - - loc, err := time.LoadLocation(zone) - if err != nil { - loc, _ = time.LoadLocation("UTC") - } - - return t.In(loc).Format(fmt) -} - -func dateModify(fmt string, date time.Time) time.Time { - d, err := time.ParseDuration(fmt) - if err != nil { - return date - } - return date.Add(d) -} - -func max(a interface{}, i ...interface{}) int64 { - aa := toInt64(a) - for _, b := range i { - bb := toInt64(b) - if bb > aa { - aa = bb - } - } - return aa -} - -func min(a interface{}, i ...interface{}) int64 { - aa := toInt64(a) - for _, b := range i { - bb := toInt64(b) - if bb < aa { - aa = bb - } - } - return aa -} - -// dfault checks whether `given` is set, and returns default if not set. -// -// This returns `d` if `given` appears not to be set, and `given` otherwise. -// -// For numeric types 0 is unset. -// For strings, maps, arrays, and slices, len() = 0 is considered unset. -// For bool, false is unset. -// Structs are never considered unset. -// -// For everything else, including pointers, a nil value is unset. -func dfault(d interface{}, given ...interface{}) interface{} { - - if empty(given) || empty(given[0]) { - return d - } - return given[0] -} - -// empty returns true if the given value has the zero value for its type. -func empty(given interface{}) bool { - g := reflect.ValueOf(given) - if !g.IsValid() { - return true - } - - // Basically adapted from text/template.isTrue - switch g.Kind() { - default: - return g.IsNil() - case reflect.Array, reflect.Slice, reflect.Map, reflect.String: - return g.Len() == 0 - case reflect.Bool: - return g.Bool() == false - case reflect.Complex64, reflect.Complex128: - return g.Complex() == 0 - case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: - return g.Int() == 0 - case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: - return g.Uint() == 0 - case reflect.Float32, reflect.Float64: - return g.Float() == 0 - case reflect.Struct: - return false - } - return true -} - -// typeIs returns true if the src is the type named in target. -func typeIs(target string, src interface{}) bool { - return target == typeOf(src) -} - -func typeIsLike(target string, src interface{}) bool { - t := typeOf(src) - return target == t || "*"+target == t -} - -func typeOf(src interface{}) string { - return fmt.Sprintf("%T", src) -} - -func kindIs(target string, src interface{}) bool { - return target == kindOf(src) -} - -func kindOf(src interface{}) string { - return reflect.ValueOf(src).Kind().String() -} - -func base64encode(v string) string { - return base64.StdEncoding.EncodeToString([]byte(v)) -} - -func base64decode(v string) string { - data, err := base64.StdEncoding.DecodeString(v) - if err != nil { - return err.Error() - } - return string(data) -} - -func base32encode(v string) string { - return base32.StdEncoding.EncodeToString([]byte(v)) -} - -func base32decode(v string) string { - data, err := base32.StdEncoding.DecodeString(v) - if err != nil { - return err.Error() - } - return string(data) -} - -func abbrev(width int, s string) string { - if width < 4 { - return s - } - r, _ := util.Abbreviate(s, width) - return r -} - -func abbrevboth(left, right int, s string) string { - if right < 4 || left > 0 && right < 7 { - return s - } - r, _ := util.AbbreviateFull(s, left, right) - return r -} -func initials(s string) string { - // Wrap this just to eliminate the var args, which templates don't do well. - return util.Initials(s) -} - -func randAlphaNumeric(count int) string { - // It is not possible, it appears, to actually generate an error here. - r, _ := util.RandomAlphaNumeric(count) - return r -} - -func randAlpha(count int) string { - r, _ := util.RandomAlphabetic(count) - return r -} - -func randAscii(count int) string { - r, _ := util.RandomAscii(count) - return r -} - -func randNumeric(count int) string { - r, _ := util.RandomNumeric(count) - return r -} - -func untitle(str string) string { - return util.Uncapitalize(str) -} - -func quote(str ...interface{}) string { - out := make([]string, len(str)) - for i, s := range str { - out[i] = fmt.Sprintf("%q", strval(s)) - } - return strings.Join(out, " ") -} - -func squote(str ...interface{}) string { - out := make([]string, len(str)) - for i, s := range str { - out[i] = fmt.Sprintf("'%v'", s) - } - return strings.Join(out, " ") -} - -func tuple(v ...interface{}) []interface{} { - return v -} - -func set(d map[string]interface{}, key string, value interface{}) map[string]interface{} { - d[key] = value - return d -} - -func unset(d map[string]interface{}, key string) map[string]interface{} { - delete(d, key) - return d -} - -func hasKey(d map[string]interface{}, key string) bool { - _, ok := d[key] - return ok -} - -func dict(v ...interface{}) map[string]interface{} { - dict := map[string]interface{}{} - lenv := len(v) - for i := 0; i < lenv; i += 2 { - key := strval(v[i]) - if i+1 >= lenv { - dict[key] = "" - continue - } - dict[key] = v[i+1] - } - return dict -} - -func strval(v interface{}) string { - switch v := v.(type) { - case string: - return v - case []byte: - return string(v) - case error: - return v.Error() - case fmt.Stringer: - return v.String() - default: - return fmt.Sprintf("%v", v) - } -} - -// toFloat64 converts 64-bit floats -func toFloat64(v interface{}) float64 { - if str, ok := v.(string); ok { - iv, err := strconv.ParseFloat(str, 64) - if err != nil { - return 0 - } - return iv - } - - val := reflect.Indirect(reflect.ValueOf(v)) - switch val.Kind() { - case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: - return float64(val.Int()) - case reflect.Uint8, reflect.Uint16, reflect.Uint32: - return float64(val.Uint()) - case reflect.Uint, reflect.Uint64: - return float64(val.Uint()) - case reflect.Float32, reflect.Float64: - return val.Float() - case reflect.Bool: - if val.Bool() == true { - return 1 - } - return 0 - default: - return 0 - } -} - -func toInt(v interface{}) int { - //It's not optimal. Bud I don't want duplicate toInt64 code. - return int(toInt64(v)) -} - -// toInt64 converts integer types to 64-bit integers -func toInt64(v interface{}) int64 { - if str, ok := v.(string); ok { - iv, err := strconv.ParseInt(str, 10, 64) - if err != nil { - return 0 - } - return iv - } - - val := reflect.Indirect(reflect.ValueOf(v)) - switch val.Kind() { - case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: - return val.Int() - case reflect.Uint8, reflect.Uint16, reflect.Uint32: - return int64(val.Uint()) - case reflect.Uint, reflect.Uint64: - tv := val.Uint() - if tv <= math.MaxInt64 { - return int64(tv) - } - // TODO: What is the sensible thing to do here? - return math.MaxInt64 - case reflect.Float32, reflect.Float64: - return int64(val.Float()) - case reflect.Bool: - if val.Bool() == true { - return 1 - } - return 0 - default: - return 0 - } -} - -func generatePrivateKey(typ string) string { - var priv interface{} - var err error - switch typ { - case "", "rsa": - // good enough for government work - priv, err = rsa.GenerateKey(rand.Reader, 4096) - case "dsa": - key := new(dsa.PrivateKey) - // again, good enough for government work - if err = dsa.GenerateParameters(&key.Parameters, rand.Reader, dsa.L2048N256); err != nil { - return fmt.Sprintf("failed to generate dsa params: %s", err) - } - err = dsa.GenerateKey(key, rand.Reader) - priv = key - case "ecdsa": - // again, good enough for government work - priv, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - default: - return "Unknown type " + typ - } - if err != nil { - return fmt.Sprintf("failed to generate private key: %s", err) - } - - return string(pem.EncodeToMemory(pemBlockForKey(priv))) -} - -type DSAKeyFormat struct { - Version int - P, Q, G, Y, X *big.Int -} - -func pemBlockForKey(priv interface{}) *pem.Block { - switch k := priv.(type) { - case *rsa.PrivateKey: - return &pem.Block{Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(k)} - case *dsa.PrivateKey: - val := DSAKeyFormat{ - P: k.P, Q: k.Q, G: k.G, - Y: k.Y, X: k.X, - } - bytes, _ := asn1.Marshal(val) - return &pem.Block{Type: "DSA PRIVATE KEY", Bytes: bytes} - case *ecdsa.PrivateKey: - b, _ := x509.MarshalECPrivateKey(k) - return &pem.Block{Type: "EC PRIVATE KEY", Bytes: b} - default: - return nil - } -} - -func trunc(c int, s string) string { - if len(s) <= c { - return s - } - return s[0:c] -} - -func cat(v ...interface{}) string { - r := strings.TrimSpace(strings.Repeat("%v ", len(v))) - return fmt.Sprintf(r, v...) -} - -func indent(spaces int, v string) string { - pad := strings.Repeat(" ", spaces) - return pad + strings.Replace(v, "\n", "\n"+pad, -1) -} - -func replace(old, new, src string) string { - return strings.Replace(src, old, new, -1) -} - -func plural(one, many string, count int) string { - if count == 1 { - return one - } - return many -} - -func sha256sum(input string) string { - hash := sha256.Sum256([]byte(input)) - return hex.EncodeToString(hash[:]) -} - -func until(count int) []int { - step := 1 - if count < 0 { - step = -1 - } - return untilStep(0, count, step) -} - -func untilStep(start, stop, step int) []int { - v := []int{} - - if stop < start { - if step >= 0 { - return v - } - for i := start; i > stop; i += step { - v = append(v, i) - } - return v - } - - if step <= 0 { - return v - } - for i := start; i < stop; i += step { - v = append(v, i) - } - return v -} -// uuidv4 provides a safe and secure UUID v4 implementation -func uuidv4() string { - return fmt.Sprintf("%s", uuid.NewV4()) + // SemVer: + "semver": semver, + "semverCompare": semverCompare, } diff --git a/vendor/github.com/Masterminds/sprig/glide.lock b/vendor/github.com/Masterminds/sprig/glide.lock index c3894b565..209cefcb4 100644 --- a/vendor/github.com/Masterminds/sprig/glide.lock +++ b/vendor/github.com/Masterminds/sprig/glide.lock @@ -1,8 +1,27 @@ -hash: a8ed42a70698b4d199b5de7fa33e7c48251651e6ccf97d007f546cb72a5d0f8f -updated: 2016-09-30T12:23:39.512939213-06:00 +hash: c2d7cb87ff32a0aba767b90bff630c3e4c1ca9904fc72568414d20d79a41e70f +updated: 2017-03-13T18:38:30.597881175-06:00 imports: - name: github.com/aokoli/goutils version: 9c37978a95bd5c709a15883b6242714ea6709e64 +- name: github.com/Masterminds/semver + version: 59c29afe1a994eacb71c833025ca7acf874bb1da - name: github.com/satori/go.uuid version: 879c5887cd475cd7864858769793b2ceb0d44feb -testImports: [] +- name: golang.org/x/crypto + version: 1f22c0103821b9390939b6776727195525381532 + subpackages: + - pbkdf2 + - scrypt +testImports: +- name: github.com/davecgh/go-spew + version: 5215b55f46b2b919f50a1df0eaa5886afe4e3b3d + subpackages: + - spew +- name: github.com/pmezard/go-difflib + version: d8ed2627bdf02c080bf22230dbb337003b7aba2d + subpackages: + - difflib +- name: github.com/stretchr/testify + version: e3a8ff8ce36581f87a15341206f205b1da467059 + subpackages: + - assert diff --git a/vendor/github.com/Masterminds/sprig/glide.yaml b/vendor/github.com/Masterminds/sprig/glide.yaml index 2fc21e24c..b6c8b6168 100644 --- a/vendor/github.com/Masterminds/sprig/glide.yaml +++ b/vendor/github.com/Masterminds/sprig/glide.yaml @@ -3,3 +3,8 @@ import: - package: github.com/aokoli/goutils - package: github.com/satori/go.uuid version: ^1.1.0 +- package: golang.org/x/crypto + subpackages: + - scrypt +- package: github.com/Masterminds/semver + version: v1.2.2 diff --git a/vendor/github.com/Masterminds/sprig/list.go b/vendor/github.com/Masterminds/sprig/list.go new file mode 100644 index 000000000..0c47b8c8a --- /dev/null +++ b/vendor/github.com/Masterminds/sprig/list.go @@ -0,0 +1,109 @@ +package sprig + +import ( + "reflect" + "sort" +) + +func list(v ...interface{}) []interface{} { + return v +} + +func push(list []interface{}, v interface{}) []interface{} { + return append(list, v) +} + +func prepend(list []interface{}, v interface{}) []interface{} { + return append([]interface{}{v}, list...) +} + +func last(list []interface{}) interface{} { + l := len(list) + if l == 0 { + return nil + } + return list[l-1] +} + +func first(list []interface{}) interface{} { + if len(list) == 0 { + return nil + } + return list[0] +} + +func rest(list []interface{}) []interface{} { + if len(list) == 0 { + return list + } + return list[1:] +} + +func initial(list []interface{}) []interface{} { + l := len(list) + if l == 0 { + return list + } + return list[:l-1] +} + +func sortAlpha(list interface{}) []string { + k := reflect.Indirect(reflect.ValueOf(list)).Kind() + switch k { + case reflect.Slice, reflect.Array: + a := strslice(list) + s := sort.StringSlice(a) + s.Sort() + return s + } + return []string{strval(list)} +} + +func reverse(v []interface{}) []interface{} { + // We do not sort in place because the incomming array should not be altered. + l := len(v) + c := make([]interface{}, l) + for i := 0; i < l; i++ { + c[l-i-1] = v[i] + } + return c +} + +func compact(list []interface{}) []interface{} { + res := []interface{}{} + for _, item := range list { + if !empty(item) { + res = append(res, item) + } + } + return res +} + +func uniq(list []interface{}) []interface{} { + dest := []interface{}{} + for _, item := range list { + if !inList(dest, item) { + dest = append(dest, item) + } + } + return dest +} + +func inList(haystack []interface{}, needle interface{}) bool { + for _, h := range haystack { + if reflect.DeepEqual(needle, h) { + return true + } + } + return false +} + +func without(list []interface{}, omit ...interface{}) []interface{} { + res := []interface{}{} + for _, i := range list { + if !inList(omit, i) { + res = append(res, i) + } + } + return res +} diff --git a/vendor/github.com/Masterminds/sprig/numeric.go b/vendor/github.com/Masterminds/sprig/numeric.go new file mode 100644 index 000000000..191e3b97a --- /dev/null +++ b/vendor/github.com/Masterminds/sprig/numeric.go @@ -0,0 +1,129 @@ +package sprig + +import ( + "math" + "reflect" + "strconv" +) + +// toFloat64 converts 64-bit floats +func toFloat64(v interface{}) float64 { + if str, ok := v.(string); ok { + iv, err := strconv.ParseFloat(str, 64) + if err != nil { + return 0 + } + return iv + } + + val := reflect.Indirect(reflect.ValueOf(v)) + switch val.Kind() { + case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: + return float64(val.Int()) + case reflect.Uint8, reflect.Uint16, reflect.Uint32: + return float64(val.Uint()) + case reflect.Uint, reflect.Uint64: + return float64(val.Uint()) + case reflect.Float32, reflect.Float64: + return val.Float() + case reflect.Bool: + if val.Bool() == true { + return 1 + } + return 0 + default: + return 0 + } +} + +func toInt(v interface{}) int { + //It's not optimal. Bud I don't want duplicate toInt64 code. + return int(toInt64(v)) +} + +// toInt64 converts integer types to 64-bit integers +func toInt64(v interface{}) int64 { + if str, ok := v.(string); ok { + iv, err := strconv.ParseInt(str, 10, 64) + if err != nil { + return 0 + } + return iv + } + + val := reflect.Indirect(reflect.ValueOf(v)) + switch val.Kind() { + case reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int: + return val.Int() + case reflect.Uint8, reflect.Uint16, reflect.Uint32: + return int64(val.Uint()) + case reflect.Uint, reflect.Uint64: + tv := val.Uint() + if tv <= math.MaxInt64 { + return int64(tv) + } + // TODO: What is the sensible thing to do here? + return math.MaxInt64 + case reflect.Float32, reflect.Float64: + return int64(val.Float()) + case reflect.Bool: + if val.Bool() == true { + return 1 + } + return 0 + default: + return 0 + } +} + +func max(a interface{}, i ...interface{}) int64 { + aa := toInt64(a) + for _, b := range i { + bb := toInt64(b) + if bb > aa { + aa = bb + } + } + return aa +} + +func min(a interface{}, i ...interface{}) int64 { + aa := toInt64(a) + for _, b := range i { + bb := toInt64(b) + if bb < aa { + aa = bb + } + } + return aa +} + +func until(count int) []int { + step := 1 + if count < 0 { + step = -1 + } + return untilStep(0, count, step) +} + +func untilStep(start, stop, step int) []int { + v := []int{} + + if stop < start { + if step >= 0 { + return v + } + for i := start; i > stop; i += step { + v = append(v, i) + } + return v + } + + if step <= 0 { + return v + } + for i := start; i < stop; i += step { + v = append(v, i) + } + return v +} diff --git a/vendor/github.com/Masterminds/sprig/reflect.go b/vendor/github.com/Masterminds/sprig/reflect.go new file mode 100644 index 000000000..8a65c132f --- /dev/null +++ b/vendor/github.com/Masterminds/sprig/reflect.go @@ -0,0 +1,28 @@ +package sprig + +import ( + "fmt" + "reflect" +) + +// typeIs returns true if the src is the type named in target. +func typeIs(target string, src interface{}) bool { + return target == typeOf(src) +} + +func typeIsLike(target string, src interface{}) bool { + t := typeOf(src) + return target == t || "*"+target == t +} + +func typeOf(src interface{}) string { + return fmt.Sprintf("%T", src) +} + +func kindIs(target string, src interface{}) bool { + return target == kindOf(src) +} + +func kindOf(src interface{}) string { + return reflect.ValueOf(src).Kind().String() +} diff --git a/vendor/github.com/Masterminds/sprig/semver.go b/vendor/github.com/Masterminds/sprig/semver.go new file mode 100644 index 000000000..c2bf8a1fd --- /dev/null +++ b/vendor/github.com/Masterminds/sprig/semver.go @@ -0,0 +1,23 @@ +package sprig + +import ( + sv2 "github.com/Masterminds/semver" +) + +func semverCompare(constraint, version string) (bool, error) { + c, err := sv2.NewConstraint(constraint) + if err != nil { + return false, err + } + + v, err := sv2.NewVersion(version) + if err != nil { + return false, err + } + + return c.Check(v), nil +} + +func semver(version string) (*sv2.Version, error) { + return sv2.NewVersion(version) +} diff --git a/vendor/github.com/Masterminds/sprig/strings.go b/vendor/github.com/Masterminds/sprig/strings.go new file mode 100644 index 000000000..69bcd9854 --- /dev/null +++ b/vendor/github.com/Masterminds/sprig/strings.go @@ -0,0 +1,197 @@ +package sprig + +import ( + "encoding/base32" + "encoding/base64" + "fmt" + "reflect" + "strconv" + "strings" + + util "github.com/aokoli/goutils" +) + +func base64encode(v string) string { + return base64.StdEncoding.EncodeToString([]byte(v)) +} + +func base64decode(v string) string { + data, err := base64.StdEncoding.DecodeString(v) + if err != nil { + return err.Error() + } + return string(data) +} + +func base32encode(v string) string { + return base32.StdEncoding.EncodeToString([]byte(v)) +} + +func base32decode(v string) string { + data, err := base32.StdEncoding.DecodeString(v) + if err != nil { + return err.Error() + } + return string(data) +} + +func abbrev(width int, s string) string { + if width < 4 { + return s + } + r, _ := util.Abbreviate(s, width) + return r +} + +func abbrevboth(left, right int, s string) string { + if right < 4 || left > 0 && right < 7 { + return s + } + r, _ := util.AbbreviateFull(s, left, right) + return r +} +func initials(s string) string { + // Wrap this just to eliminate the var args, which templates don't do well. + return util.Initials(s) +} + +func randAlphaNumeric(count int) string { + // It is not possible, it appears, to actually generate an error here. + r, _ := util.RandomAlphaNumeric(count) + return r +} + +func randAlpha(count int) string { + r, _ := util.RandomAlphabetic(count) + return r +} + +func randAscii(count int) string { + r, _ := util.RandomAscii(count) + return r +} + +func randNumeric(count int) string { + r, _ := util.RandomNumeric(count) + return r +} + +func untitle(str string) string { + return util.Uncapitalize(str) +} + +func quote(str ...interface{}) string { + out := make([]string, len(str)) + for i, s := range str { + out[i] = fmt.Sprintf("%q", strval(s)) + } + return strings.Join(out, " ") +} + +func squote(str ...interface{}) string { + out := make([]string, len(str)) + for i, s := range str { + out[i] = fmt.Sprintf("'%v'", s) + } + return strings.Join(out, " ") +} + +func cat(v ...interface{}) string { + r := strings.TrimSpace(strings.Repeat("%v ", len(v))) + return fmt.Sprintf(r, v...) +} + +func indent(spaces int, v string) string { + pad := strings.Repeat(" ", spaces) + return pad + strings.Replace(v, "\n", "\n"+pad, -1) +} + +func replace(old, new, src string) string { + return strings.Replace(src, old, new, -1) +} + +func plural(one, many string, count int) string { + if count == 1 { + return one + } + return many +} + +func strslice(v interface{}) []string { + switch v := v.(type) { + case []string: + return v + case []interface{}: + l := len(v) + b := make([]string, l) + for i := 0; i < l; i++ { + b[i] = strval(v[i]) + } + return b + default: + val := reflect.ValueOf(v) + switch val.Kind() { + case reflect.Array, reflect.Slice: + l := val.Len() + b := make([]string, l) + for i := 0; i < l; i++ { + b[i] = strval(val.Index(i).Interface()) + } + return b + default: + return []string{strval(v)} + } + } +} + +func strval(v interface{}) string { + switch v := v.(type) { + case string: + return v + case []byte: + return string(v) + case error: + return v.Error() + case fmt.Stringer: + return v.String() + default: + return fmt.Sprintf("%v", v) + } +} + +func trunc(c int, s string) string { + if len(s) <= c { + return s + } + return s[0:c] +} + +func join(sep string, v interface{}) string { + return strings.Join(strslice(v), sep) +} + +func split(sep, orig string) map[string]string { + parts := strings.Split(orig, sep) + res := make(map[string]string, len(parts)) + for i, v := range parts { + res["_"+strconv.Itoa(i)] = v + } + return res +} + +// substring creates a substring of the given string. +// +// If start is < 0, this calls string[:length]. +// +// If start is >= 0 and length < 0, this calls string[start:] +// +// Otherwise, this calls string[start, length]. +func substring(start, length int, s string) string { + if start < 0 { + return s[:length] + } + if length < 0 { + return s[start:] + } + return s[start:length] +} From 5688ea894fe6958ebcbc6c567d36f9a79d971817 Mon Sep 17 00:00:00 2001 From: David Chung Date: Fri, 5 May 2017 01:29:03 -0700 Subject: [PATCH 3/3] vendor --- vendor.conf | 3 + .../github.com/Masterminds/semver/.travis.yml | 25 + .../Masterminds/semver/CHANGELOG.md | 62 +++ .../github.com/Masterminds/semver/LICENSE.txt | 20 + vendor/github.com/Masterminds/semver/Makefile | 36 ++ .../github.com/Masterminds/semver/README.md | 165 +++++++ .../Masterminds/semver/appveyor.yml | 44 ++ .../Masterminds/semver/collection.go | 24 + .../Masterminds/semver/constraints.go | 426 ++++++++++++++++++ vendor/github.com/Masterminds/semver/doc.go | 115 +++++ .../github.com/Masterminds/semver/version.go | 401 +++++++++++++++++ vendor/github.com/graymeta/stow/.gitignore | 28 ++ vendor/github.com/graymeta/stow/.travis.yml | 14 + .../github.com/graymeta/stow/BestPractices.md | 31 ++ vendor/github.com/graymeta/stow/LICENSE | 201 +++++++++ vendor/github.com/graymeta/stow/Makefile | 34 ++ vendor/github.com/graymeta/stow/README.md | 219 +++++++++ vendor/github.com/graymeta/stow/doc.go | 2 + .../github.com/graymeta/stow/google/README.md | 64 +++ .../github.com/graymeta/stow/google/config.go | 76 ++++ .../graymeta/stow/google/container.go | 197 ++++++++ vendor/github.com/graymeta/stow/google/doc.go | 42 ++ .../github.com/graymeta/stow/google/item.go | 88 ++++ .../graymeta/stow/google/location.go | 128 ++++++ .../graymeta/stow/local/container.go | 173 +++++++ vendor/github.com/graymeta/stow/local/doc.go | 34 ++ .../graymeta/stow/local/filedata_darwin.go | 78 ++++ .../graymeta/stow/local/filedata_linux.go | 78 ++++ .../graymeta/stow/local/filedata_windows.go | 42 ++ vendor/github.com/graymeta/stow/local/item.go | 107 +++++ .../github.com/graymeta/stow/local/local.go | 45 ++ .../graymeta/stow/local/location.go | 150 ++++++ vendor/github.com/graymeta/stow/s3/README.md | 67 +++ vendor/github.com/graymeta/stow/s3/config.go | 132 ++++++ .../github.com/graymeta/stow/s3/container.go | 262 +++++++++++ vendor/github.com/graymeta/stow/s3/doc.go | 44 ++ vendor/github.com/graymeta/stow/s3/item.go | 148 ++++++ .../github.com/graymeta/stow/s3/location.go | 192 ++++++++ .../graymeta/stow/stow-aeroplane.png | Bin 0 -> 41735 bytes .../graymeta/stow/stow-definition.png | Bin 0 -> 35143 bytes vendor/github.com/graymeta/stow/stow.go | 233 ++++++++++ vendor/github.com/graymeta/stow/walk.go | 81 ++++ vendor/golang.org/x/crypto/scrypt/scrypt.go | 243 ++++++++++ 43 files changed, 4554 insertions(+) create mode 100644 vendor/github.com/Masterminds/semver/.travis.yml create mode 100644 vendor/github.com/Masterminds/semver/CHANGELOG.md create mode 100644 vendor/github.com/Masterminds/semver/LICENSE.txt create mode 100644 vendor/github.com/Masterminds/semver/Makefile create mode 100644 vendor/github.com/Masterminds/semver/README.md create mode 100644 vendor/github.com/Masterminds/semver/appveyor.yml create mode 100644 vendor/github.com/Masterminds/semver/collection.go create mode 100644 vendor/github.com/Masterminds/semver/constraints.go create mode 100644 vendor/github.com/Masterminds/semver/doc.go create mode 100644 vendor/github.com/Masterminds/semver/version.go create mode 100644 vendor/github.com/graymeta/stow/.gitignore create mode 100644 vendor/github.com/graymeta/stow/.travis.yml create mode 100644 vendor/github.com/graymeta/stow/BestPractices.md create mode 100644 vendor/github.com/graymeta/stow/LICENSE create mode 100644 vendor/github.com/graymeta/stow/Makefile create mode 100644 vendor/github.com/graymeta/stow/README.md create mode 100644 vendor/github.com/graymeta/stow/doc.go create mode 100644 vendor/github.com/graymeta/stow/google/README.md create mode 100644 vendor/github.com/graymeta/stow/google/config.go create mode 100644 vendor/github.com/graymeta/stow/google/container.go create mode 100644 vendor/github.com/graymeta/stow/google/doc.go create mode 100644 vendor/github.com/graymeta/stow/google/item.go create mode 100644 vendor/github.com/graymeta/stow/google/location.go create mode 100644 vendor/github.com/graymeta/stow/local/container.go create mode 100644 vendor/github.com/graymeta/stow/local/doc.go create mode 100644 vendor/github.com/graymeta/stow/local/filedata_darwin.go create mode 100644 vendor/github.com/graymeta/stow/local/filedata_linux.go create mode 100644 vendor/github.com/graymeta/stow/local/filedata_windows.go create mode 100644 vendor/github.com/graymeta/stow/local/item.go create mode 100644 vendor/github.com/graymeta/stow/local/local.go create mode 100644 vendor/github.com/graymeta/stow/local/location.go create mode 100644 vendor/github.com/graymeta/stow/s3/README.md create mode 100644 vendor/github.com/graymeta/stow/s3/config.go create mode 100644 vendor/github.com/graymeta/stow/s3/container.go create mode 100644 vendor/github.com/graymeta/stow/s3/doc.go create mode 100644 vendor/github.com/graymeta/stow/s3/item.go create mode 100644 vendor/github.com/graymeta/stow/s3/location.go create mode 100644 vendor/github.com/graymeta/stow/stow-aeroplane.png create mode 100644 vendor/github.com/graymeta/stow/stow-definition.png create mode 100644 vendor/github.com/graymeta/stow/stow.go create mode 100644 vendor/github.com/graymeta/stow/walk.go create mode 100644 vendor/golang.org/x/crypto/scrypt/scrypt.go diff --git a/vendor.conf b/vendor.conf index 5de32c5b4..bdf18a27c 100644 --- a/vendor.conf +++ b/vendor.conf @@ -8,6 +8,7 @@ github.com/docker/infrakit github.com/Masterminds/sprig 2.10.0 +github.com/Masterminds/semver v1.3.0 github.com/Microsoft/go-winio 0.3.6 github.com/Sirupsen/logrus v0.11.0-5-gabc6f20 github.com/aokoli/goutils 1.0.0 @@ -33,6 +34,7 @@ github.com/golang/protobuf/jsonpb 4bd1920723d7b7c925de087a github.com/golang/protobuf/proto 4bd1920723d7b7c925de087aa32e2187708897f7 github.com/gorilla/mux 757bef9 github.com/gorilla/rpc 22c016f +github.com/graymeta/stow 0f16e17 github.com/grpc-ecosystem/go-grpc-prometheus v1.1 github.com/grpc-ecosystem/grpc-gateway/runtime 84398b94e188ee336f307779b57b3aa91af7063c github.com/grpc-ecosystem/grpc-gateway/utilities 84398b94e188ee336f307779b57b3aa91af7063c @@ -73,6 +75,7 @@ github.com/stretchr/testify v1.1.4-4-g976c720 github.com/twmb/algoimpl 716f8e7 github.com/vaughan0/go-ini a98ad7e golang.org/x/crypto/pbkdf2 b07d8c9 +golang.org/x/crypto/scrypt aa2481c golang.org/x/net 0dd7c8d golang.org/x/sys b699b70 golang.org/x/text a263ba8 diff --git a/vendor/github.com/Masterminds/semver/.travis.yml b/vendor/github.com/Masterminds/semver/.travis.yml new file mode 100644 index 000000000..09ccf0e44 --- /dev/null +++ b/vendor/github.com/Masterminds/semver/.travis.yml @@ -0,0 +1,25 @@ +language: go + +go: + - 1.6 + - 1.7 + - 1.8 + - tip + +# Setting sudo access to false will let Travis CI use containers rather than +# VMs to run the tests. For more details see: +# - http://docs.travis-ci.com/user/workers/container-based-infrastructure/ +# - http://docs.travis-ci.com/user/workers/standard-infrastructure/ +sudo: false + +script: + - GO15VENDOREXPERIMENT=1 make setup + - GO15VENDOREXPERIMENT=1 make test + +notifications: + webhooks: + urls: + - https://webhooks.gitter.im/e/06e3328629952dabe3e0 + on_success: change # options: [always|never|change] default: always + on_failure: always # options: [always|never|change] default: always + on_start: never # options: [always|never|change] default: always diff --git a/vendor/github.com/Masterminds/semver/CHANGELOG.md b/vendor/github.com/Masterminds/semver/CHANGELOG.md new file mode 100644 index 000000000..31d3893b3 --- /dev/null +++ b/vendor/github.com/Masterminds/semver/CHANGELOG.md @@ -0,0 +1,62 @@ +# 1.3.0 (2017-05-02) + +## Added +- #45: Added json (un)marshaling support (thanks @mh-cbon) +- Stability marker. See https://masterminds.github.io/stability/ + +## Fixed +- #51: Fix handling of single digit tilde constraint (thanks @dgodd) + +## Changed +- #55: The godoc icon moved from png to svg + +# 1.2.3 (2017-04-03) + +## Fixed +- #46: Fixed 0.x.x and 0.0.x in constraints being treated as * + +# Release 1.2.2 (2016-12-13) + +## Fixed +- #34: Fixed issue where hyphen range was not working with pre-release parsing. + +# Release 1.2.1 (2016-11-28) + +## Fixed +- #24: Fixed edge case issue where constraint "> 0" does not handle "0.0.1-alpha" + properly. + +# Release 1.2.0 (2016-11-04) + +## Added +- #20: Added MustParse function for versions (thanks @adamreese) +- #15: Added increment methods on versions (thanks @mh-cbon) + +## Fixed +- Issue #21: Per the SemVer spec (section 9) a pre-release is unstable and + might not satisfy the intended compatibility. The change here ignores pre-releases + on constraint checks (e.g., ~ or ^) when a pre-release is not part of the + constraint. For example, `^1.2.3` will ignore pre-releases while + `^1.2.3-alpha` will include them. + +# Release 1.1.1 (2016-06-30) + +## Changed +- Issue #9: Speed up version comparison performance (thanks @sdboyer) +- Issue #8: Added benchmarks (thanks @sdboyer) +- Updated Go Report Card URL to new location +- Updated Readme to add code snippet formatting (thanks @mh-cbon) +- Updating tagging to v[SemVer] structure for compatibility with other tools. + +# Release 1.1.0 (2016-03-11) + +- Issue #2: Implemented validation to provide reasons a versions failed a + constraint. + +# Release 1.0.1 (2015-12-31) + +- Fixed #1: * constraint failing on valid versions. + +# Release 1.0.0 (2015-10-20) + +- Initial release diff --git a/vendor/github.com/Masterminds/semver/LICENSE.txt b/vendor/github.com/Masterminds/semver/LICENSE.txt new file mode 100644 index 000000000..0da4aeadb --- /dev/null +++ b/vendor/github.com/Masterminds/semver/LICENSE.txt @@ -0,0 +1,20 @@ +The Masterminds +Copyright (C) 2014-2015, Matt Butcher and Matt Farina + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/github.com/Masterminds/semver/Makefile b/vendor/github.com/Masterminds/semver/Makefile new file mode 100644 index 000000000..a7a1b4e36 --- /dev/null +++ b/vendor/github.com/Masterminds/semver/Makefile @@ -0,0 +1,36 @@ +.PHONY: setup +setup: + go get -u gopkg.in/alecthomas/gometalinter.v1 + gometalinter.v1 --install + +.PHONY: test +test: validate lint + @echo "==> Running tests" + go test -v + +.PHONY: validate +validate: + @echo "==> Running static validations" + @gometalinter.v1 \ + --disable-all \ + --enable deadcode \ + --severity deadcode:error \ + --enable gofmt \ + --enable gosimple \ + --enable ineffassign \ + --enable misspell \ + --enable vet \ + --tests \ + --vendor \ + --deadline 60s \ + ./... || exit_code=1 + +.PHONY: lint +lint: + @echo "==> Running linters" + @gometalinter.v1 \ + --disable-all \ + --enable golint \ + --vendor \ + --deadline 60s \ + ./... || : diff --git a/vendor/github.com/Masterminds/semver/README.md b/vendor/github.com/Masterminds/semver/README.md new file mode 100644 index 000000000..3e934ed71 --- /dev/null +++ b/vendor/github.com/Masterminds/semver/README.md @@ -0,0 +1,165 @@ +# SemVer + +The `semver` package provides the ability to work with [Semantic Versions](http://semver.org) in Go. Specifically it provides the ability to: + +* Parse semantic versions +* Sort semantic versions +* Check if a semantic version fits within a set of constraints +* Optionally work with a `v` prefix + +[![Stability: +Active](https://masterminds.github.io/stability/active.svg)](https://masterminds.github.io/stability/active.html) +[![Build Status](https://travis-ci.org/Masterminds/semver.svg)](https://travis-ci.org/Masterminds/semver) [![Build status](https://ci.appveyor.com/api/projects/status/jfk66lib7hb985k8/branch/master?svg=true&passingText=windows%20build%20passing&failingText=windows%20build%20failing)](https://ci.appveyor.com/project/mattfarina/semver/branch/master) [![GoDoc](https://godoc.org/github.com/Masterminds/semver?status.svg)](https://godoc.org/github.com/Masterminds/semver) [![Go Report Card](https://goreportcard.com/badge/github.com/Masterminds/semver)](https://goreportcard.com/report/github.com/Masterminds/semver) + +## Parsing Semantic Versions + +To parse a semantic version use the `NewVersion` function. For example, + +```go + v, err := semver.NewVersion("1.2.3-beta.1+build345") +``` + +If there is an error the version wasn't parseable. The version object has methods +to get the parts of the version, compare it to other versions, convert the +version back into a string, and get the original string. For more details +please see the [documentation](https://godoc.org/github.com/Masterminds/semver). + +## Sorting Semantic Versions + +A set of versions can be sorted using the [`sort`](https://golang.org/pkg/sort/) +package from the standard library. For example, + +```go + raw := []string{"1.2.3", "1.0", "1.3", "2", "0.4.2",} + vs := make([]*semver.Version, len(raw)) + for i, r := range raw { + v, err := semver.NewVersion(r) + if err != nil { + t.Errorf("Error parsing version: %s", err) + } + + vs[i] = v + } + + sort.Sort(semver.Collection(vs)) +``` + +## Checking Version Constraints + +Checking a version against version constraints is one of the most featureful +parts of the package. + +```go + c, err := semver.NewConstraint(">= 1.2.3") + if err != nil { + // Handle constraint not being parseable. + } + + v, _ := semver.NewVersion("1.3") + if err != nil { + // Handle version not being parseable. + } + // Check if the version meets the constraints. The a variable will be true. + a := c.Check(v) +``` + +## Basic Comparisons + +There are two elements to the comparisons. First, a comparison string is a list +of comma separated and comparisons. These are then separated by || separated or +comparisons. For example, `">= 1.2, < 3.0.0 || >= 4.2.3"` is looking for a +comparison that's greater than or equal to 1.2 and less than 3.0.0 or is +greater than or equal to 4.2.3. + +The basic comparisons are: + +* `=`: equal (aliased to no operator) +* `!=`: not equal +* `>`: greater than +* `<`: less than +* `>=`: greater than or equal to +* `<=`: less than or equal to + +_Note, according to the Semantic Version specification pre-releases may not be +API compliant with their release counterpart. It says,_ + +> _A pre-release version indicates that the version is unstable and might not satisfy the intended compatibility requirements as denoted by its associated normal version._ + +_SemVer comparisons without a pre-release value will skip pre-release versions. +For example, `>1.2.3` will skip pre-releases when looking at a list of values +while `>1.2.3-alpha.1` will evaluate pre-releases._ + +## Hyphen Range Comparisons + +There are multiple methods to handle ranges and the first is hyphens ranges. +These look like: + +* `1.2 - 1.4.5` which is equivalent to `>= 1.2, <= 1.4.5` +* `2.3.4 - 4.5` which is equivalent to `>= 2.3.4, <= 4.5` + +## Wildcards In Comparisons + +The `x`, `X`, and `*` characters can be used as a wildcard character. This works +for all comparison operators. When used on the `=` operator it falls +back to the pack level comparison (see tilde below). For example, + +* `1.2.x` is equivalent to `>= 1.2.0, < 1.3.0` +* `>= 1.2.x` is equivalent to `>= 1.2.0` +* `<= 2.x` is equivalent to `<= 3` +* `*` is equivalent to `>= 0.0.0` + +## Tilde Range Comparisons (Patch) + +The tilde (`~`) comparison operator is for patch level ranges when a minor +version is specified and major level changes when the minor number is missing. +For example, + +* `~1.2.3` is equivalent to `>= 1.2.3, < 1.3.0` +* `~1` is equivalent to `>= 1, < 2` +* `~2.3` is equivalent to `>= 2.3, < 2.4` +* `~1.2.x` is equivalent to `>= 1.2.0, < 1.3.0` +* `~1.x` is equivalent to `>= 1, < 2` + +## Caret Range Comparisons (Major) + +The caret (`^`) comparison operator is for major level changes. This is useful +when comparisons of API versions as a major change is API breaking. For example, + +* `^1.2.3` is equivalent to `>= 1.2.3, < 2.0.0` +* `^1.2.x` is equivalent to `>= 1.2.0, < 2.0.0` +* `^2.3` is equivalent to `>= 2.3, < 3` +* `^2.x` is equivalent to `>= 2.0.0, < 3` + +# Validation + +In addition to testing a version against a constraint, a version can be validated +against a constraint. When validation fails a slice of errors containing why a +version didn't meet the constraint is returned. For example, + +```go + c, err := semver.NewConstraint("<= 1.2.3, >= 1.4") + if err != nil { + // Handle constraint not being parseable. + } + + v, _ := semver.NewVersion("1.3") + if err != nil { + // Handle version not being parseable. + } + + // Validate a version against a constraint. + a, msgs := c.Validate(v) + // a is false + for _, m := range msgs { + fmt.Println(m) + + // Loops over the errors which would read + // "1.3 is greater than 1.2.3" + // "1.3 is less than 1.4" + } +``` + +# Contribute + +If you find an issue or want to contribute please file an [issue](https://github.com/Masterminds/semver/issues) +or [create a pull request](https://github.com/Masterminds/semver/pulls). diff --git a/vendor/github.com/Masterminds/semver/appveyor.yml b/vendor/github.com/Masterminds/semver/appveyor.yml new file mode 100644 index 000000000..b2778df15 --- /dev/null +++ b/vendor/github.com/Masterminds/semver/appveyor.yml @@ -0,0 +1,44 @@ +version: build-{build}.{branch} + +clone_folder: C:\gopath\src\github.com\Masterminds\semver +shallow_clone: true + +environment: + GOPATH: C:\gopath + +platform: + - x64 + +install: + - go version + - go env + - go get -u gopkg.in/alecthomas/gometalinter.v1 + - set PATH=%PATH%;%GOPATH%\bin + - gometalinter.v1.exe --install + +build_script: + - go install -v ./... + +test_script: + - "gometalinter.v1 \ + --disable-all \ + --enable deadcode \ + --severity deadcode:error \ + --enable gofmt \ + --enable gosimple \ + --enable ineffassign \ + --enable misspell \ + --enable vet \ + --tests \ + --vendor \ + --deadline 60s \ + ./... || exit_code=1" + - "gometalinter.v1 \ + --disable-all \ + --enable golint \ + --vendor \ + --deadline 60s \ + ./... || :" + - go test -v + +deploy: off diff --git a/vendor/github.com/Masterminds/semver/collection.go b/vendor/github.com/Masterminds/semver/collection.go new file mode 100644 index 000000000..a78235895 --- /dev/null +++ b/vendor/github.com/Masterminds/semver/collection.go @@ -0,0 +1,24 @@ +package semver + +// Collection is a collection of Version instances and implements the sort +// interface. See the sort package for more details. +// https://golang.org/pkg/sort/ +type Collection []*Version + +// Len returns the length of a collection. The number of Version instances +// on the slice. +func (c Collection) Len() int { + return len(c) +} + +// Less is needed for the sort interface to compare two Version objects on the +// slice. If checks if one is less than the other. +func (c Collection) Less(i, j int) bool { + return c[i].LessThan(c[j]) +} + +// Swap is needed for the sort interface to replace the Version objects +// at two different positions in the slice. +func (c Collection) Swap(i, j int) { + c[i], c[j] = c[j], c[i] +} diff --git a/vendor/github.com/Masterminds/semver/constraints.go b/vendor/github.com/Masterminds/semver/constraints.go new file mode 100644 index 000000000..a41a6a7a4 --- /dev/null +++ b/vendor/github.com/Masterminds/semver/constraints.go @@ -0,0 +1,426 @@ +package semver + +import ( + "errors" + "fmt" + "regexp" + "strings" +) + +// Constraints is one or more constraint that a semantic version can be +// checked against. +type Constraints struct { + constraints [][]*constraint +} + +// NewConstraint returns a Constraints instance that a Version instance can +// be checked against. If there is a parse error it will be returned. +func NewConstraint(c string) (*Constraints, error) { + + // Rewrite - ranges into a comparison operation. + c = rewriteRange(c) + + ors := strings.Split(c, "||") + or := make([][]*constraint, len(ors)) + for k, v := range ors { + cs := strings.Split(v, ",") + result := make([]*constraint, len(cs)) + for i, s := range cs { + pc, err := parseConstraint(s) + if err != nil { + return nil, err + } + + result[i] = pc + } + or[k] = result + } + + o := &Constraints{constraints: or} + return o, nil +} + +// Check tests if a version satisfies the constraints. +func (cs Constraints) Check(v *Version) bool { + // loop over the ORs and check the inner ANDs + for _, o := range cs.constraints { + joy := true + for _, c := range o { + if !c.check(v) { + joy = false + break + } + } + + if joy { + return true + } + } + + return false +} + +// Validate checks if a version satisfies a constraint. If not a slice of +// reasons for the failure are returned in addition to a bool. +func (cs Constraints) Validate(v *Version) (bool, []error) { + // loop over the ORs and check the inner ANDs + var e []error + for _, o := range cs.constraints { + joy := true + for _, c := range o { + if !c.check(v) { + em := fmt.Errorf(c.msg, v, c.orig) + e = append(e, em) + joy = false + } + } + + if joy { + return true, []error{} + } + } + + return false, e +} + +var constraintOps map[string]cfunc +var constraintMsg map[string]string +var constraintRegex *regexp.Regexp + +func init() { + constraintOps = map[string]cfunc{ + "": constraintTildeOrEqual, + "=": constraintTildeOrEqual, + "!=": constraintNotEqual, + ">": constraintGreaterThan, + "<": constraintLessThan, + ">=": constraintGreaterThanEqual, + "=>": constraintGreaterThanEqual, + "<=": constraintLessThanEqual, + "=<": constraintLessThanEqual, + "~": constraintTilde, + "~>": constraintTilde, + "^": constraintCaret, + } + + constraintMsg = map[string]string{ + "": "%s is not equal to %s", + "=": "%s is not equal to %s", + "!=": "%s is equal to %s", + ">": "%s is less than or equal to %s", + "<": "%s is greater than or equal to %s", + ">=": "%s is less than %s", + "=>": "%s is less than %s", + "<=": "%s is greater than %s", + "=<": "%s is greater than %s", + "~": "%s does not have same major and minor version as %s", + "~>": "%s does not have same major and minor version as %s", + "^": "%s does not have same major version as %s", + } + + ops := make([]string, 0, len(constraintOps)) + for k := range constraintOps { + ops = append(ops, regexp.QuoteMeta(k)) + } + + constraintRegex = regexp.MustCompile(fmt.Sprintf( + `^\s*(%s)\s*(%s)\s*$`, + strings.Join(ops, "|"), + cvRegex)) + + constraintRangeRegex = regexp.MustCompile(fmt.Sprintf( + `\s*(%s)\s+-\s+(%s)\s*`, + cvRegex, cvRegex)) +} + +// An individual constraint +type constraint struct { + // The callback function for the restraint. It performs the logic for + // the constraint. + function cfunc + + msg string + + // The version used in the constraint check. For example, if a constraint + // is '<= 2.0.0' the con a version instance representing 2.0.0. + con *Version + + // The original parsed version (e.g., 4.x from != 4.x) + orig string + + // When an x is used as part of the version (e.g., 1.x) + minorDirty bool + dirty bool + patchDirty bool +} + +// Check if a version meets the constraint +func (c *constraint) check(v *Version) bool { + return c.function(v, c) +} + +type cfunc func(v *Version, c *constraint) bool + +func parseConstraint(c string) (*constraint, error) { + m := constraintRegex.FindStringSubmatch(c) + if m == nil { + return nil, fmt.Errorf("improper constraint: %s", c) + } + + ver := m[2] + orig := ver + minorDirty := false + patchDirty := false + dirty := false + if isX(m[3]) { + ver = "0.0.0" + dirty = true + } else if isX(strings.TrimPrefix(m[4], ".")) || m[4] == "" { + minorDirty = true + dirty = true + ver = fmt.Sprintf("%s.0.0%s", m[3], m[6]) + } else if isX(strings.TrimPrefix(m[5], ".")) { + dirty = true + patchDirty = true + ver = fmt.Sprintf("%s%s.0%s", m[3], m[4], m[6]) + } + + con, err := NewVersion(ver) + if err != nil { + + // The constraintRegex should catch any regex parsing errors. So, + // we should never get here. + return nil, errors.New("constraint Parser Error") + } + + cs := &constraint{ + function: constraintOps[m[1]], + msg: constraintMsg[m[1]], + con: con, + orig: orig, + minorDirty: minorDirty, + patchDirty: patchDirty, + dirty: dirty, + } + return cs, nil +} + +// Constraint functions +func constraintNotEqual(v *Version, c *constraint) bool { + if c.dirty { + + // If there is a pre-release on the version but the constraint isn't looking + // for them assume that pre-releases are not compatible. See issue 21 for + // more details. + if v.Prerelease() != "" && c.con.Prerelease() == "" { + return false + } + + if c.con.Major() != v.Major() { + return true + } + if c.con.Minor() != v.Minor() && !c.minorDirty { + return true + } else if c.minorDirty { + return false + } + + return false + } + + return !v.Equal(c.con) +} + +func constraintGreaterThan(v *Version, c *constraint) bool { + + // An edge case the constraint is 0.0.0 and the version is 0.0.0-someprerelease + // exists. This that case. + if !isNonZero(c.con) && isNonZero(v) { + return true + } + + // If there is a pre-release on the version but the constraint isn't looking + // for them assume that pre-releases are not compatible. See issue 21 for + // more details. + if v.Prerelease() != "" && c.con.Prerelease() == "" { + return false + } + + return v.Compare(c.con) == 1 +} + +func constraintLessThan(v *Version, c *constraint) bool { + // If there is a pre-release on the version but the constraint isn't looking + // for them assume that pre-releases are not compatible. See issue 21 for + // more details. + if v.Prerelease() != "" && c.con.Prerelease() == "" { + return false + } + + if !c.dirty { + return v.Compare(c.con) < 0 + } + + if v.Major() > c.con.Major() { + return false + } else if v.Minor() > c.con.Minor() && !c.minorDirty { + return false + } + + return true +} + +func constraintGreaterThanEqual(v *Version, c *constraint) bool { + // An edge case the constraint is 0.0.0 and the version is 0.0.0-someprerelease + // exists. This that case. + if !isNonZero(c.con) && isNonZero(v) { + return true + } + + // If there is a pre-release on the version but the constraint isn't looking + // for them assume that pre-releases are not compatible. See issue 21 for + // more details. + if v.Prerelease() != "" && c.con.Prerelease() == "" { + return false + } + + return v.Compare(c.con) >= 0 +} + +func constraintLessThanEqual(v *Version, c *constraint) bool { + // If there is a pre-release on the version but the constraint isn't looking + // for them assume that pre-releases are not compatible. See issue 21 for + // more details. + if v.Prerelease() != "" && c.con.Prerelease() == "" { + return false + } + + if !c.dirty { + return v.Compare(c.con) <= 0 + } + + if v.Major() > c.con.Major() { + return false + } else if v.Minor() > c.con.Minor() && !c.minorDirty { + return false + } + + return true +} + +// ~*, ~>* --> >= 0.0.0 (any) +// ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0, <3.0.0 +// ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0, <2.1.0 +// ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0, <1.3.0 +// ~1.2.3, ~>1.2.3 --> >=1.2.3, <1.3.0 +// ~1.2.0, ~>1.2.0 --> >=1.2.0, <1.3.0 +func constraintTilde(v *Version, c *constraint) bool { + // If there is a pre-release on the version but the constraint isn't looking + // for them assume that pre-releases are not compatible. See issue 21 for + // more details. + if v.Prerelease() != "" && c.con.Prerelease() == "" { + return false + } + + if v.LessThan(c.con) { + return false + } + + // ~0.0.0 is a special case where all constraints are accepted. It's + // equivalent to >= 0.0.0. + if c.con.Major() == 0 && c.con.Minor() == 0 && c.con.Patch() == 0 && + !c.minorDirty && !c.patchDirty { + return true + } + + if v.Major() != c.con.Major() { + return false + } + + if v.Minor() != c.con.Minor() && !c.minorDirty { + return false + } + + return true +} + +// When there is a .x (dirty) status it automatically opts in to ~. Otherwise +// it's a straight = +func constraintTildeOrEqual(v *Version, c *constraint) bool { + // If there is a pre-release on the version but the constraint isn't looking + // for them assume that pre-releases are not compatible. See issue 21 for + // more details. + if v.Prerelease() != "" && c.con.Prerelease() == "" { + return false + } + + if c.dirty { + c.msg = constraintMsg["~"] + return constraintTilde(v, c) + } + + return v.Equal(c.con) +} + +// ^* --> (any) +// ^2, ^2.x, ^2.x.x --> >=2.0.0, <3.0.0 +// ^2.0, ^2.0.x --> >=2.0.0, <3.0.0 +// ^1.2, ^1.2.x --> >=1.2.0, <2.0.0 +// ^1.2.3 --> >=1.2.3, <2.0.0 +// ^1.2.0 --> >=1.2.0, <2.0.0 +func constraintCaret(v *Version, c *constraint) bool { + // If there is a pre-release on the version but the constraint isn't looking + // for them assume that pre-releases are not compatible. See issue 21 for + // more details. + if v.Prerelease() != "" && c.con.Prerelease() == "" { + return false + } + + if v.LessThan(c.con) { + return false + } + + if v.Major() != c.con.Major() { + return false + } + + return true +} + +var constraintRangeRegex *regexp.Regexp + +const cvRegex string = `v?([0-9|x|X|\*]+)(\.[0-9|x|X|\*]+)?(\.[0-9|x|X|\*]+)?` + + `(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` + + `(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` + +func isX(x string) bool { + switch x { + case "x", "*", "X": + return true + default: + return false + } +} + +func rewriteRange(i string) string { + m := constraintRangeRegex.FindAllStringSubmatch(i, -1) + if m == nil { + return i + } + o := i + for _, v := range m { + t := fmt.Sprintf(">= %s, <= %s", v[1], v[11]) + o = strings.Replace(o, v[0], t, 1) + } + + return o +} + +// Detect if a version is not zero (0.0.0) +func isNonZero(v *Version) bool { + if v.Major() != 0 || v.Minor() != 0 || v.Patch() != 0 || v.Prerelease() != "" { + return true + } + + return false +} diff --git a/vendor/github.com/Masterminds/semver/doc.go b/vendor/github.com/Masterminds/semver/doc.go new file mode 100644 index 000000000..e00f65eb7 --- /dev/null +++ b/vendor/github.com/Masterminds/semver/doc.go @@ -0,0 +1,115 @@ +/* +Package semver provides the ability to work with Semantic Versions (http://semver.org) in Go. + +Specifically it provides the ability to: + + * Parse semantic versions + * Sort semantic versions + * Check if a semantic version fits within a set of constraints + * Optionally work with a `v` prefix + +Parsing Semantic Versions + +To parse a semantic version use the `NewVersion` function. For example, + + v, err := semver.NewVersion("1.2.3-beta.1+build345") + +If there is an error the version wasn't parseable. The version object has methods +to get the parts of the version, compare it to other versions, convert the +version back into a string, and get the original string. For more details +please see the documentation at https://godoc.org/github.com/Masterminds/semver. + +Sorting Semantic Versions + +A set of versions can be sorted using the `sort` package from the standard library. +For example, + + raw := []string{"1.2.3", "1.0", "1.3", "2", "0.4.2",} + vs := make([]*semver.Version, len(raw)) + for i, r := range raw { + v, err := semver.NewVersion(r) + if err != nil { + t.Errorf("Error parsing version: %s", err) + } + + vs[i] = v + } + + sort.Sort(semver.Collection(vs)) + +Checking Version Constraints + +Checking a version against version constraints is one of the most featureful +parts of the package. + + c, err := semver.NewConstraint(">= 1.2.3") + if err != nil { + // Handle constraint not being parseable. + } + + v, _ := semver.NewVersion("1.3") + if err != nil { + // Handle version not being parseable. + } + // Check if the version meets the constraints. The a variable will be true. + a := c.Check(v) + +Basic Comparisons + +There are two elements to the comparisons. First, a comparison string is a list +of comma separated and comparisons. These are then separated by || separated or +comparisons. For example, `">= 1.2, < 3.0.0 || >= 4.2.3"` is looking for a +comparison that's greater than or equal to 1.2 and less than 3.0.0 or is +greater than or equal to 4.2.3. + +The basic comparisons are: + + * `=`: equal (aliased to no operator) + * `!=`: not equal + * `>`: greater than + * `<`: less than + * `>=`: greater than or equal to + * `<=`: less than or equal to + +Hyphen Range Comparisons + +There are multiple methods to handle ranges and the first is hyphens ranges. +These look like: + + * `1.2 - 1.4.5` which is equivalent to `>= 1.2, <= 1.4.5` + * `2.3.4 - 4.5` which is equivalent to `>= 2.3.4, <= 4.5` + +Wildcards In Comparisons + +The `x`, `X`, and `*` characters can be used as a wildcard character. This works +for all comparison operators. When used on the `=` operator it falls +back to the pack level comparison (see tilde below). For example, + + * `1.2.x` is equivalent to `>= 1.2.0, < 1.3.0` + * `>= 1.2.x` is equivalent to `>= 1.2.0` + * `<= 2.x` is equivalent to `<= 3` + * `*` is equivalent to `>= 0.0.0` + +Tilde Range Comparisons (Patch) + +The tilde (`~`) comparison operator is for patch level ranges when a minor +version is specified and major level changes when the minor number is missing. +For example, + + * `~1.2.3` is equivalent to `>= 1.2.3, < 1.3.0` + * `~1` is equivalent to `>= 1, < 2` + * `~2.3` is equivalent to `>= 2.3, < 2.4` + * `~1.2.x` is equivalent to `>= 1.2.0, < 1.3.0` + * `~1.x` is equivalent to `>= 1, < 2` + +Caret Range Comparisons (Major) + +The caret (`^`) comparison operator is for major level changes. This is useful +when comparisons of API versions as a major change is API breaking. For example, + + * `^1.2.3` is equivalent to `>= 1.2.3, < 2.0.0` + * `^1.2.x` is equivalent to `>= 1.2.0, < 2.0.0` + * `^2.3` is equivalent to `>= 2.3, < 3` + * `^2.x` is equivalent to `>= 2.0.0, < 3` +*/ +package semver diff --git a/vendor/github.com/Masterminds/semver/version.go b/vendor/github.com/Masterminds/semver/version.go new file mode 100644 index 000000000..48b2162fd --- /dev/null +++ b/vendor/github.com/Masterminds/semver/version.go @@ -0,0 +1,401 @@ +package semver + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "regexp" + "strconv" + "strings" +) + +// The compiled version of the regex created at init() is cached here so it +// only needs to be created once. +var versionRegex *regexp.Regexp +var validPrereleaseRegex *regexp.Regexp + +var ( + // ErrInvalidSemVer is returned a version is found to be invalid when + // being parsed. + ErrInvalidSemVer = errors.New("Invalid Semantic Version") + + // ErrInvalidMetadata is returned when the metadata is an invalid format + ErrInvalidMetadata = errors.New("Invalid Metadata string") + + // ErrInvalidPrerelease is returned when the pre-release is an invalid format + ErrInvalidPrerelease = errors.New("Invalid Prerelease string") +) + +// SemVerRegex is the regular expression used to parse a semantic version. +const SemVerRegex string = `v?([0-9]+)(\.[0-9]+)?(\.[0-9]+)?` + + `(-([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` + + `(\+([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*))?` + +// ValidPrerelease is the regular expression which validates +// both prerelease and metadata values. +const ValidPrerelease string = `^([0-9A-Za-z\-]+(\.[0-9A-Za-z\-]+)*)` + +// Version represents a single semantic version. +type Version struct { + major, minor, patch int64 + pre string + metadata string + original string +} + +func init() { + versionRegex = regexp.MustCompile("^" + SemVerRegex + "$") + validPrereleaseRegex = regexp.MustCompile(ValidPrerelease) +} + +// NewVersion parses a given version and returns an instance of Version or +// an error if unable to parse the version. +func NewVersion(v string) (*Version, error) { + m := versionRegex.FindStringSubmatch(v) + if m == nil { + return nil, ErrInvalidSemVer + } + + sv := &Version{ + metadata: m[8], + pre: m[5], + original: v, + } + + var temp int64 + temp, err := strconv.ParseInt(m[1], 10, 32) + if err != nil { + return nil, fmt.Errorf("Error parsing version segment: %s", err) + } + sv.major = temp + + if m[2] != "" { + temp, err = strconv.ParseInt(strings.TrimPrefix(m[2], "."), 10, 32) + if err != nil { + return nil, fmt.Errorf("Error parsing version segment: %s", err) + } + sv.minor = temp + } else { + sv.minor = 0 + } + + if m[3] != "" { + temp, err = strconv.ParseInt(strings.TrimPrefix(m[3], "."), 10, 32) + if err != nil { + return nil, fmt.Errorf("Error parsing version segment: %s", err) + } + sv.patch = temp + } else { + sv.patch = 0 + } + + return sv, nil +} + +// MustParse parses a given version and panics on error. +func MustParse(v string) *Version { + sv, err := NewVersion(v) + if err != nil { + panic(err) + } + return sv +} + +// String converts a Version object to a string. +// Note, if the original version contained a leading v this version will not. +// See the Original() method to retrieve the original value. Semantic Versions +// don't contain a leading v per the spec. Instead it's optional on +// impelementation. +func (v *Version) String() string { + var buf bytes.Buffer + + fmt.Fprintf(&buf, "%d.%d.%d", v.major, v.minor, v.patch) + if v.pre != "" { + fmt.Fprintf(&buf, "-%s", v.pre) + } + if v.metadata != "" { + fmt.Fprintf(&buf, "+%s", v.metadata) + } + + return buf.String() +} + +// Original returns the original value passed in to be parsed. +func (v *Version) Original() string { + return v.original +} + +// Major returns the major version. +func (v *Version) Major() int64 { + return v.major +} + +// Minor returns the minor version. +func (v *Version) Minor() int64 { + return v.minor +} + +// Patch returns the patch version. +func (v *Version) Patch() int64 { + return v.patch +} + +// Prerelease returns the pre-release version. +func (v *Version) Prerelease() string { + return v.pre +} + +// Metadata returns the metadata on the version. +func (v *Version) Metadata() string { + return v.metadata +} + +// originalVPrefix returns the original 'v' prefix if any. +func (v *Version) originalVPrefix() string { + + // Note, only lowercase v is supported as a prefix by the parser. + if v.original != "" && v.original[:1] == "v" { + return v.original[:1] + } + return "" +} + +// IncPatch produces the next patch version. +// If the current version does not have prerelease/metadata information, +// it unsets metadata and prerelease values, increments patch number. +// If the current version has any of prerelease or metadata information, +// it unsets both values and keeps curent patch value +func (v Version) IncPatch() Version { + vNext := v + // according to http://semver.org/#spec-item-9 + // Pre-release versions have a lower precedence than the associated normal version. + // according to http://semver.org/#spec-item-10 + // Build metadata SHOULD be ignored when determining version precedence. + if v.pre != "" { + vNext.metadata = "" + vNext.pre = "" + } else { + vNext.metadata = "" + vNext.pre = "" + vNext.patch = v.patch + 1 + } + vNext.original = v.originalVPrefix() + "" + vNext.String() + return vNext +} + +// IncMinor produces the next minor version. +// Sets patch to 0. +// Increments minor number. +// Unsets metadata. +// Unsets prerelease status. +func (v Version) IncMinor() Version { + vNext := v + vNext.metadata = "" + vNext.pre = "" + vNext.patch = 0 + vNext.minor = v.minor + 1 + vNext.original = v.originalVPrefix() + "" + vNext.String() + return vNext +} + +// IncMajor produces the next major version. +// Sets patch to 0. +// Sets minor to 0. +// Increments major number. +// Unsets metadata. +// Unsets prerelease status. +func (v Version) IncMajor() Version { + vNext := v + vNext.metadata = "" + vNext.pre = "" + vNext.patch = 0 + vNext.minor = 0 + vNext.major = v.major + 1 + vNext.original = v.originalVPrefix() + "" + vNext.String() + return vNext +} + +// SetPrerelease defines the prerelease value. +// Value must not include the required 'hypen' prefix. +func (v Version) SetPrerelease(prerelease string) (Version, error) { + vNext := v + if len(prerelease) > 0 && !validPrereleaseRegex.MatchString(prerelease) { + return vNext, ErrInvalidPrerelease + } + vNext.pre = prerelease + vNext.original = v.originalVPrefix() + "" + vNext.String() + return vNext, nil +} + +// SetMetadata defines metadata value. +// Value must not include the required 'plus' prefix. +func (v Version) SetMetadata(metadata string) (Version, error) { + vNext := v + if len(metadata) > 0 && !validPrereleaseRegex.MatchString(metadata) { + return vNext, ErrInvalidMetadata + } + vNext.metadata = metadata + vNext.original = v.originalVPrefix() + "" + vNext.String() + return vNext, nil +} + +// LessThan tests if one version is less than another one. +func (v *Version) LessThan(o *Version) bool { + return v.Compare(o) < 0 +} + +// GreaterThan tests if one version is greater than another one. +func (v *Version) GreaterThan(o *Version) bool { + return v.Compare(o) > 0 +} + +// Equal tests if two versions are equal to each other. +// Note, versions can be equal with different metadata since metadata +// is not considered part of the comparable version. +func (v *Version) Equal(o *Version) bool { + return v.Compare(o) == 0 +} + +// Compare compares this version to another one. It returns -1, 0, or 1 if +// the version smaller, equal, or larger than the other version. +// +// Versions are compared by X.Y.Z. Build metadata is ignored. Prerelease is +// lower than the version without a prerelease. +func (v *Version) Compare(o *Version) int { + // Compare the major, minor, and patch version for differences. If a + // difference is found return the comparison. + if d := compareSegment(v.Major(), o.Major()); d != 0 { + return d + } + if d := compareSegment(v.Minor(), o.Minor()); d != 0 { + return d + } + if d := compareSegment(v.Patch(), o.Patch()); d != 0 { + return d + } + + // At this point the major, minor, and patch versions are the same. + ps := v.pre + po := o.Prerelease() + + if ps == "" && po == "" { + return 0 + } + if ps == "" { + return 1 + } + if po == "" { + return -1 + } + + return comparePrerelease(ps, po) +} + +// UnmarshalJSON implements JSON.Unmarshaler interface. +func (v *Version) UnmarshalJSON(b []byte) error { + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + temp, err := NewVersion(s) + if err != nil { + return err + } + v.major = temp.major + v.minor = temp.minor + v.patch = temp.patch + v.pre = temp.pre + v.metadata = temp.metadata + v.original = temp.original + temp = nil + return nil +} + +// MarshalJSON implements JSON.Marshaler interface. +func (v *Version) MarshalJSON() ([]byte, error) { + return json.Marshal(v.String()) +} + +func compareSegment(v, o int64) int { + if v < o { + return -1 + } + if v > o { + return 1 + } + + return 0 +} + +func comparePrerelease(v, o string) int { + + // split the prelease versions by their part. The separator, per the spec, + // is a . + sparts := strings.Split(v, ".") + oparts := strings.Split(o, ".") + + // Find the longer length of the parts to know how many loop iterations to + // go through. + slen := len(sparts) + olen := len(oparts) + + l := slen + if olen > slen { + l = olen + } + + // Iterate over each part of the prereleases to compare the differences. + for i := 0; i < l; i++ { + // Since the lentgh of the parts can be different we need to create + // a placeholder. This is to avoid out of bounds issues. + stemp := "" + if i < slen { + stemp = sparts[i] + } + + otemp := "" + if i < olen { + otemp = oparts[i] + } + + d := comparePrePart(stemp, otemp) + if d != 0 { + return d + } + } + + // Reaching here means two versions are of equal value but have different + // metadata (the part following a +). They are not identical in string form + // but the version comparison finds them to be equal. + return 0 +} + +func comparePrePart(s, o string) int { + // Fastpath if they are equal + if s == o { + return 0 + } + + // When s or o are empty we can use the other in an attempt to determine + // the response. + if o == "" { + _, n := strconv.ParseInt(s, 10, 64) + if n != nil { + return -1 + } + return 1 + } + if s == "" { + _, n := strconv.ParseInt(o, 10, 64) + if n != nil { + return 1 + } + return -1 + } + + if s > o { + return 1 + } + return -1 +} diff --git a/vendor/github.com/graymeta/stow/.gitignore b/vendor/github.com/graymeta/stow/.gitignore new file mode 100644 index 000000000..db2bf01df --- /dev/null +++ b/vendor/github.com/graymeta/stow/.gitignore @@ -0,0 +1,28 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof + +tests.out +tests.xml +.idea \ No newline at end of file diff --git a/vendor/github.com/graymeta/stow/.travis.yml b/vendor/github.com/graymeta/stow/.travis.yml new file mode 100644 index 000000000..efe219d89 --- /dev/null +++ b/vendor/github.com/graymeta/stow/.travis.yml @@ -0,0 +1,14 @@ +language: go + +sudo: required + +before_install: + - sudo apt-get -qq update + - sudo apt-get install -y lsof + +go: + - tip + +install: go get -v ./... + +script: go test -v ./... diff --git a/vendor/github.com/graymeta/stow/BestPractices.md b/vendor/github.com/graymeta/stow/BestPractices.md new file mode 100644 index 000000000..ff712d02f --- /dev/null +++ b/vendor/github.com/graymeta/stow/BestPractices.md @@ -0,0 +1,31 @@ +# Best practices + +## Configuring Stow once + +It is recommended that you create a single file that imports Stow and all the implementations to save you from doing so in every code file where you might use Stow. You can also take the opportunity to abstract the top level Stow methods that your code uses to save other code files (including other packages) from importing Stow at all. + +Create a file called `storage.go` in your package and add the following code: + +```go +import ( + "github.com/graymeta/stow" + // support Azure storage + _ "github.com/graymeta/stow/azure" + // support Google storage + _ "github.com/graymeta/stow/google" + // support local storage + _ "github.com/graymeta/stow/local" + // support swift storage + _ "github.com/graymeta/stow/swift" + // support s3 storage + _ "github.com/graymeta/stow/s3" + // support oracle storage + _ "github.com/graymeta/stow/oracle" +) + +// Dial dials stow storage. +// See stow.Dial for more information. +func Dial(kind string, config stow.Config) (stow.Location, error) { + return stow.Dial(kind, config) +} +``` diff --git a/vendor/github.com/graymeta/stow/LICENSE b/vendor/github.com/graymeta/stow/LICENSE new file mode 100644 index 000000000..c8cb3e258 --- /dev/null +++ b/vendor/github.com/graymeta/stow/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2016 GrayMeta, Inc. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/graymeta/stow/Makefile b/vendor/github.com/graymeta/stow/Makefile new file mode 100644 index 000000000..a9d143446 --- /dev/null +++ b/vendor/github.com/graymeta/stow/Makefile @@ -0,0 +1,34 @@ +.PHONY: test +WORKSPACE = $(shell pwd) + +topdir = /tmp/$(pkg)-$(version) + +all: container runcontainer + @true + +container: + docker build --no-cache -t builder-stow test/ + +runcontainer: + docker run -v $(WORKSPACE):/mnt/src/github.com/graymeta/stow builder-stow + +deps: + go get github.com/tebeka/go2xunit + go get github.com/Azure/azure-sdk-for-go/storage + go get github.com/aws/aws-sdk-go + go get github.com/ncw/swift + go get github.com/cheekybits/is + go get golang.org/x/net/context + go get golang.org/x/oauth2/google + go get github.com/pkg/errors + go get google.golang.org/api/storage/... + +test: clean deps vet + go test -v ./... | tee tests.out + go2xunit -fail -input tests.out -output tests.xml + +vet: + go vet ./... + +clean: + rm -f tests.out test.xml diff --git a/vendor/github.com/graymeta/stow/README.md b/vendor/github.com/graymeta/stow/README.md new file mode 100644 index 000000000..a7d4f4139 --- /dev/null +++ b/vendor/github.com/graymeta/stow/README.md @@ -0,0 +1,219 @@ +![Stow logo](stow-aeroplane.png) +![Stow definition](stow-definition.png) +[![GoDoc](https://godoc.org/github.com/graymeta/stow?status.svg)](https://godoc.org/github.com/graymeta/stow) +[![Go Report Card](https://goreportcard.com/badge/github.com/graymeta/stow)](https://goreportcard.com/report/github.com/graymeta/stow) + +Cloud storage abstraction package for Go. + +* Version: 0.1.0 +* Project status: Stable. Approaching v1 release +* It is recommended that you vendor this package as changes are possible before v1 + +* Contributors: [Mat Ryer](https://github.com/matryer), [David Hernandez](https://github.com/dahernan), [Ernesto Jiménez](https://github.com/ernesto-jimenez), [Corey Prak](https://github.com/Xercoy), [Piotr Rojek](https://github.com/piotrrojek), [Jason Hancock](https://github.com/jasonhancock) +* Artwork by Mel Jensen inspired by [Renee French](http://reneefrench.blogspot.co.uk) and and is licensed under the [Creative Commons Attribution 3.0 License](https://creativecommons.org/licenses/by/3.0/) + +## How it works + +Stow provides implementations for storage services, blob stores, cloud storage etc. Read the [blog post announcing the project](https://medium.com/@matryer/introducing-stow-cloud-storage-abstraction-package-for-go-20cf2928d93c). + +## Implementations + +* Local (folders are containers, files are items) +* Amazon S3 +* Google Cloud Storage +* Microsoft Azure Blob Storage +* Openstack Swift (with auth v2) +* Oracle Storage Cloud Service + +## Concepts + +The concepts of Stow are modeled around the most popular object storage services, and are made up of three main objects: + +* `Location` - a place where many `Container` objects are stored +* `Container` - a named group of `Item` objects +* `Item` - an individual file + +``` +location1 (e.g. Azure) +├── container1 +├───── item1.1 +├───── item1.2 +├───── item1.3 +├── container2 +├───── item2.1 +├───── item2.2 +location2 (e.g. local storage) +├── container1 +├───── item1.1 +├───── item1.2 +├───── item1.3 +├── container2 +├───── item2.1 +├───── item2.2 +``` + +* A location contains many containers +* A container contains many items +* Containers do not contain other containers +* Items must belong to a container +* Item names may be a path + +## Guides + +* [Using Stow](#using-stow) +* [Connecting to locations](#connecting-to-locations) +* [Walking containers](#walking-containers) +* [Walking items](#walking-items) +* [Downloading a file](#downloading-afile) +* [Uploading a file](#uploading-a-file) +* [Stow URLs](#stow-urls) +* [Cursors](#cursors) + +### Using Stow + +Import Stow plus any of the implementation packages that you wish to provide. For example, to support Google Cloud Storage and Amazon S3 you would write: + +```go +import ( + "github.com/graymeta/stow" + _ "github.com/graymeta/stow/google" + _ "github.com/graymeta/stow/s3" +) +``` + +The underscore indicates that you do not intend to use the package in your code. Importing it is enough, as the implementation packages register themselves with Stow during initialization. + +* For more information about using Stow, see the [Best practices documentation](BestPractices.md). +* Some implementation packages provide ways to access the underlying connection details for use-cases where more control over a specific service is needed. See the implementation package documentation for details. + +### Connecting to locations + +To connect to a location, you need to know the `kind` string (available by accessing the `Kind` constant in the implementation package) and a `stow.Config` object that contains any required configuration information (such as account names, API keys, credentials, etc). Configuration is implementation specific, so you should consult each implementation to see what fields are required. + +```go +kind := "s3" +config := stow.ConfigMap{ + s3.ConfigAccessKeyID: "246810" + s3.ConfigSecretKey: "abc123", + s3.ConfigRegion: "eu-west-1" +} +location, err := stow.Dial(kind, config) +if err != nil { + return err +} +defer location.Close() + +// TODO: use location +``` + +### Walking containers + +You can walk every Container using the `stow.WalkContainers` function: + +```go +func WalkContainers(location Location, prefix string, pageSize int, fn WalkContainersFunc) error +``` + +For example: + +```go +err = stow.WalkContainers(location, stow.NoPrefix, 100, func(c stow.Container, err error) error { + if err != nil { + return err + } + switch c.Name() { + case c1.Name(), c2.Name(), c3.Name(): + found++ + } + return nil +}) +if err != nil { + return err +} +``` + +### Walking items + +Once you have a `Container`, you can walk every Item inside it using the `stow.Walk` function: + +```go +func Walk(container Container, prefix string, pageSize int, fn WalkFunc) error +``` + +For example: + +```go +err = stow.Walk(containers[0], stow.NoPrefix, 100, func(item stow.Item, err error) error { + if err != nil { + return err + } + log.Println(item.Name()) + return nil +}) +if err != nil { + return err +} +``` + +### Downloading a file + +Once you have found a `stow.Item` that you are interested in, you can stream its contents by first calling the `Open` method and reading from the returned `io.ReadCloser` (remembering to close the reader): + +```go +r, err := item.Open() +if err != nil { + return err +} +defer r.Close() + +// TODO: stream the contents by reading from r +``` + +### Uploading a file + +If you want to write a new item into a Container, you can do so using the `container.Put` method passing in an `io.Reader` for the contents along with the size: + +```go +contents := "This is a new file stored in the cloud" +r := strings.NewReader(contents) +size := int64(len(contents)) + +item, err := container.Put(name, r, size, nil) +if err != nil { + return err +} + +// item represents the newly created/updated item +``` + +### Stow URLs + +An `Item` can return a URL via the `URL()` method. While a valid URL, they are useful only within the context of Stow. Within a Location, you can get items using these URLs via the `Location.ItemByURL` method. + +#### Getting an `Item` by URL + +If you have a Stow URL, you can use it to lookup the kind of location: + +```go +kind, err := stow.KindByURL(url) +``` + +`kind` will be a string describing the kind of storage. You can then pass `kind` along with a `Config` to `stow.New` to create a new `Location` where the item for the URL is: + +```go +location, err := stow.Dial(kind, config) +``` + +You can then get the `Item` for the specified URL from the location: + +```go +item, err := location.ItemByURL(url) +``` + +### Cursors + +Cursors are strings that provide a pointer to items in sets allowing for paging over the entire set. + +Call such methods first passing in `stow.CursorStart` as the cursor, which indicates the first item/page. The method will, as one of its return arguments, provide a new cursor which you can pass into subsequent calls to the same method. + +When `stow.IsCursorEnd(cursor)` returns `true`, you have reached the end of the set. diff --git a/vendor/github.com/graymeta/stow/doc.go b/vendor/github.com/graymeta/stow/doc.go new file mode 100644 index 000000000..2535cc835 --- /dev/null +++ b/vendor/github.com/graymeta/stow/doc.go @@ -0,0 +1,2 @@ +// Package stow provides an abstraction on cloud storage capabilities. +package stow diff --git a/vendor/github.com/graymeta/stow/google/README.md b/vendor/github.com/graymeta/stow/google/README.md new file mode 100644 index 000000000..05872b717 --- /dev/null +++ b/vendor/github.com/graymeta/stow/google/README.md @@ -0,0 +1,64 @@ +# Google Cloud Storage Stow Implementation + +Location = Google Cloud Storage + +Container = Bucket + +Item = File + +## How to access underlying service types + +Use a type conversion to extract the underlying `Location`, `Container`, or `Item` implementations. Then use the Google-specific getters to access the internal Google Cloud Storage `Service`, `Bucket`, and `Object` values. + +```go +import ( + "log" + "github.com/graymeta/stow" + stowgs "github.com/graymeta/stow/google" +) + +stowLoc, err := stow.Dial(stowgs.Kind, stow.ConfigMap{ + stowgs.ConfigJSON: "", + stowgs.ConfigProjectId: "", +}) +if err != nil { + log.Fatal(err) +} + +stowBucket, err = stowLoc.Container("mybucket") +if err != nil { + log.Fatal(err) +} + +if gsBucket, ok := stowBucket.(*stowgs.Bucket); ok { + if gsLoc, ok := stowLoc.(*stowgs.Location); ok { + + googleService := gsLoc.Service() + googleBucket, err := gsBucket.Bucket() + + // < Send platform-specific commands here > + + } +} +``` + +By default, Stow uses `https://www.googleapis.com/auth/devstorage.read_write` scope. Different scopes can be used by passing a comma separated list of scopes, like below: +```go +stowLoc, err := stow.Dial(stowgs.Kind, stow.ConfigMap{ + stowgs.ConfigJSON: "", + stowgs.ConfigProjectId: "", + stowgs.ConfigScopes: ",", +}) +``` + +--- + +Configuration... You need to create a project in google, and then create a service account in google tied to that project. You will need to download a `.json` file with the configuration for the service account. To run the test suite, the service account will need edit privileges inside the project. + +To run the test suite, set the `GOOGLE_CREDENTIALS_FILE` environment variable to point to the location of the .json file containing the service account credentials and set `GOOGLE_PROJECT_ID` to the project ID, otherwise the test suite will not be run. + +--- + +Concerns: + +- Google's storage plaform is more _eventually consistent_ than other platforms. Sometimes, the tests appear to be flaky because of this. One example is when deleting files from a bucket, then immediately deleting the bucket...sometimes the bucket delete will fail saying that the bucket isn't empty simply because the file delete messages haven't propagated through Google's infrastructure. We may need to add some delay into the test suite to account for this. diff --git a/vendor/github.com/graymeta/stow/google/config.go b/vendor/github.com/graymeta/stow/google/config.go new file mode 100644 index 000000000..4a0614542 --- /dev/null +++ b/vendor/github.com/graymeta/stow/google/config.go @@ -0,0 +1,76 @@ +package google + +import ( + "errors" + "net/url" + "strings" + + "github.com/graymeta/stow" + "golang.org/x/net/context" + "golang.org/x/oauth2/google" + storage "google.golang.org/api/storage/v1" +) + +// Kind represents the name of the location/storage type. +const Kind = "google" + +const ( + // The service account json blob + ConfigJSON = "json" + ConfigProjectId = "project_id" + ConfigScopes = "scopes" +) + +func init() { + + makefn := func(config stow.Config) (stow.Location, error) { + _, ok := config.Config(ConfigJSON) + if !ok { + return nil, errors.New("missing JSON configuration") + } + + _, ok = config.Config(ConfigProjectId) + if !ok { + return nil, errors.New("missing Project ID") + } + + // Create a new client + client, err := newGoogleStorageClient(config) + if err != nil { + return nil, err + } + + // Create a location with given config and client + loc := &Location{ + config: config, + client: client, + } + + return loc, nil + } + + kindfn := func(u *url.URL) bool { + return u.Scheme == Kind + } + + stow.Register(Kind, makefn, kindfn) +} + +// Attempts to create a session based on the information given. +func newGoogleStorageClient(config stow.Config) (*storage.Service, error) { + json, _ := config.Config(ConfigJSON) + + scopes := []string{storage.DevstorageReadWriteScope} + if s, ok := config.Config(ConfigScopes); ok && s != "" { + scopes = strings.Split(s, ",") + } + + jwtConf, err := google.JWTConfigFromJSON([]byte(json), scopes...) + + service, err := storage.New(jwtConf.Client(context.Background())) + if err != nil { + return nil, err + } + + return service, nil +} diff --git a/vendor/github.com/graymeta/stow/google/container.go b/vendor/github.com/graymeta/stow/google/container.go new file mode 100644 index 000000000..27610c69d --- /dev/null +++ b/vendor/github.com/graymeta/stow/google/container.go @@ -0,0 +1,197 @@ +package google + +import ( + "io" + "time" + + "github.com/graymeta/stow" + "github.com/pkg/errors" + storage "google.golang.org/api/storage/v1" +) + +type Container struct { + // Name is needed to retrieve items. + name string + + // Client is responsible for performing the requests. + client *storage.Service +} + +// ID returns a string value which represents the name of the container. +func (c *Container) ID() string { + return c.name +} + +// Name returns a string value which represents the name of the container. +func (c *Container) Name() string { + return c.name +} + +func (c *Container) Bucket() (*storage.Bucket, error) { + return c.client.Buckets.Get(c.name).Do() +} + +// Item returns a stow.Item instance of a container based on the +// name of the container +func (c *Container) Item(id string) (stow.Item, error) { + res, err := c.client.Objects.Get(c.name, id).Do() + if err != nil { + return nil, stow.ErrNotFound + } + + t, err := time.Parse(time.RFC3339, res.Updated) + if err != nil { + return nil, err + } + + u, err := prepUrl(res.MediaLink) + if err != nil { + return nil, err + } + + mdParsed, err := parseMetadata(res.Metadata) + if err != nil { + return nil, err + } + + i := &Item{ + name: id, + container: c, + client: c.client, + size: int64(res.Size), + etag: res.Etag, + hash: res.Md5Hash, + lastModified: t, + url: u, + metadata: mdParsed, + object: res, + } + + return i, nil +} + +// Items retrieves a list of items that are prepended with +// the prefix argument. The 'cursor' variable facilitates pagination. +func (c *Container) Items(prefix string, cursor string, count int) ([]stow.Item, string, error) { + // List all objects in a bucket using pagination + call := c.client.Objects.List(c.name).MaxResults(int64(count)) + + if prefix != "" { + call.Prefix(prefix) + } + + if cursor != "" { + call = call.PageToken(cursor) + } + + res, err := call.Do() + if err != nil { + return nil, "", err + } + containerItems := make([]stow.Item, len(res.Items)) + + for i, o := range res.Items { + t, err := time.Parse(time.RFC3339, o.Updated) + if err != nil { + return nil, "", err + } + + u, err := prepUrl(o.MediaLink) + if err != nil { + return nil, "", err + } + + mdParsed, err := parseMetadata(o.Metadata) + if err != nil { + return nil, "", err + } + + containerItems[i] = &Item{ + name: o.Name, + container: c, + client: c.client, + size: int64(o.Size), + etag: o.Etag, + hash: o.Md5Hash, + lastModified: t, + url: u, + metadata: mdParsed, + object: o, + } + } + + return containerItems, res.NextPageToken, nil +} + +func (c *Container) RemoveItem(id string) error { + return c.client.Objects.Delete(c.name, id).Do() +} + +// Put sends a request to upload content to the container. The arguments +// received are the name of the item, a reader representing the +// content, and the size of the file. +func (c *Container) Put(name string, r io.Reader, size int64, metadata map[string]interface{}) (stow.Item, error) { + mdPrepped, err := prepMetadata(metadata) + if err != nil { + return nil, err + } + + object := &storage.Object{ + Name: name, + Metadata: mdPrepped, + } + + res, err := c.client.Objects.Insert(c.name, object).Media(r).Do() + if err != nil { + return nil, err + } + + t, err := time.Parse(time.RFC3339, res.Updated) + if err != nil { + return nil, err + } + + u, err := prepUrl(res.MediaLink) + if err != nil { + return nil, err + } + + mdParsed, err := parseMetadata(res.Metadata) + if err != nil { + return nil, err + } + + newItem := &Item{ + name: name, + container: c, + client: c.client, + size: size, + etag: res.Etag, + hash: res.Md5Hash, + lastModified: t, + url: u, + metadata: mdParsed, + object: res, + } + return newItem, nil +} + +func parseMetadata(metadataParsed map[string]string) (map[string]interface{}, error) { + metadataParsedMap := make(map[string]interface{}, len(metadataParsed)) + for key, value := range metadataParsed { + metadataParsedMap[key] = value + } + return metadataParsedMap, nil +} + +func prepMetadata(metadataParsed map[string]interface{}) (map[string]string, error) { + returnMap := make(map[string]string, len(metadataParsed)) + for key, value := range metadataParsed { + str, ok := value.(string) + if !ok { + return nil, errors.Errorf(`value of key '%s' in metadata must be of type string`, key) + } + returnMap[key] = str + } + return returnMap, nil +} diff --git a/vendor/github.com/graymeta/stow/google/doc.go b/vendor/github.com/graymeta/stow/google/doc.go new file mode 100644 index 000000000..8c1c3c596 --- /dev/null +++ b/vendor/github.com/graymeta/stow/google/doc.go @@ -0,0 +1,42 @@ +/* +Package google provides an abstraction of Google Cloud Storage. In this package, a Google Cloud Storage Bucket is represented by a Stow Container and a Google Cloud Storage Object is represented by a Stow Item. Note that directories may exist within a Bucket. + +Usage and Credentials + +A path to the JSON file representing configuration information for the service account is needed, as well as the Project ID that it is tied to. + +stow.Dial requires both a string value of the particular Stow Location Kind ("google") and a stow.Config instance. The stow.Config instance requires two entries with the specific key value attributes: + +- a key of google.ConfigJSON with a value of the path of the JSON configuration file +- a key of google.ConfigProjectID with a value of the Project ID + +Location + +There are google.location methods which allow the retrieval of a Google Cloud Storage Object (Container or Containers). An Object can also be retrieved based on the its URL (ItemByURL). + +Additional google.location methods provide capabilities to create and remove Google Cloud Storage Buckets (CreateContainer or RemoveContainer). + +Container + +Methods of stow.container allow the retrieval of a Google Bucket's: + +- name(ID or Name) +- object or complete list of objects (Item or Items, respectively) + +Additional methods of google.container allow Stow to: + +- remove an Object (RemoveItem) +- update or create an Object (Put) + +Item + +Methods of google.Item allow the retrieval of a Google Cloud Storage Object's: +- name (ID or name) +- URL +- size in bytes +- Object specific metadata (information stored within the Google Cloud Service) +- last modified date +- Etag +- content +*/ +package google diff --git a/vendor/github.com/graymeta/stow/google/item.go b/vendor/github.com/graymeta/stow/google/item.go new file mode 100644 index 000000000..fc28d95ba --- /dev/null +++ b/vendor/github.com/graymeta/stow/google/item.go @@ -0,0 +1,88 @@ +package google + +import ( + "io" + "net/url" + + // "strings" + "time" + + storage "google.golang.org/api/storage/v1" +) + +type Item struct { + container *Container // Container information is required by a few methods. + client *storage.Service // A client is needed to make requests. + name string + hash string + etag string + size int64 + url *url.URL + lastModified time.Time + metadata map[string]interface{} + object *storage.Object +} + +// ID returns a string value that represents the name of a file. +func (i *Item) ID() string { + return i.name +} + +// Name returns a string value that represents the name of the file. +func (i *Item) Name() string { + return i.name +} + +// Size returns the size of an item in bytes. +func (i *Item) Size() (int64, error) { + return i.size, nil +} + +// URL returns a url which follows the predefined format +func (i *Item) URL() *url.URL { + return i.url +} + +// Open returns an io.ReadCloser to the object. Useful for downloading/streaming the object. +func (i *Item) Open() (io.ReadCloser, error) { + res, err := i.client.Objects.Get(i.container.name, i.name).Download() + if err != nil { + return nil, err + } + + return res.Body, nil +} + +// LastMod returns the last modified date of the item. +func (i *Item) LastMod() (time.Time, error) { + return i.lastModified, nil +} + +// Metadata returns a nil map and no error. +// TODO: Implement this. +func (i *Item) Metadata() (map[string]interface{}, error) { + return i.metadata, nil +} + +// ETag returns the ETag value +func (i *Item) ETag() (string, error) { + return i.etag, nil +} + +// Object returns the Google Storage Object +func (i *Item) StorageObject() *storage.Object { + return i.object +} + +// prepUrl takes a MediaLink string and returns a url +func prepUrl(str string) (*url.URL, error) { + u, err := url.Parse(str) + if err != nil { + return nil, err + } + u.Scheme = "google" + + // Discard the query string + u.RawQuery = "" + return u, nil +} diff --git a/vendor/github.com/graymeta/stow/google/location.go b/vendor/github.com/graymeta/stow/google/location.go new file mode 100644 index 000000000..e2776406e --- /dev/null +++ b/vendor/github.com/graymeta/stow/google/location.go @@ -0,0 +1,128 @@ +package google + +import ( + "errors" + "net/url" + "strings" + + "github.com/graymeta/stow" + storage "google.golang.org/api/storage/v1" +) + +// A Location contains a client + the configurations used to create the client. +type Location struct { + config stow.Config + client *storage.Service +} + +func (l *Location) Service() *storage.Service { + return l.client +} + +// Close simply satisfies the Location interface. There's nothing that +// needs to be done in order to satisfy the interface. +func (l *Location) Close() error { + return nil // nothing to close +} + +// CreateContainer creates a new container, in this case a bucket. +func (l *Location) CreateContainer(containerName string) (stow.Container, error) { + + projId, _ := l.config.Config(ConfigProjectId) + // Create a bucket. + _, err := l.client.Buckets.Insert(projId, &storage.Bucket{Name: containerName}).Do() + //res, err := l.client.Buckets.Insert(projId, &storage.Bucket{Name: containerName}).Do() + if err != nil { + return nil, err + } + + newContainer := &Container{ + name: containerName, + client: l.client, + } + + return newContainer, nil +} + +// Containers returns a slice of the Container interface, a cursor, and an error. +func (l *Location) Containers(prefix string, cursor string, count int) ([]stow.Container, string, error) { + + projId, _ := l.config.Config(ConfigProjectId) + + // List all objects in a bucket using pagination + call := l.client.Buckets.List(projId).MaxResults(int64(count)) + + if prefix != "" { + call.Prefix(prefix) + } + + if cursor != "" { + call = call.PageToken(cursor) + } + + res, err := call.Do() + if err != nil { + return nil, "", err + } + containers := make([]stow.Container, len(res.Items)) + + for i, o := range res.Items { + containers[i] = &Container{ + name: o.Name, + client: l.client, + } + } + + return containers, res.NextPageToken, nil +} + +// Container retrieves a stow.Container based on its name which must be +// exact. +func (l *Location) Container(id string) (stow.Container, error) { + + _, err := l.client.Buckets.Get(id).Do() + if err != nil { + return nil, stow.ErrNotFound + } + + c := &Container{ + name: id, + client: l.client, + } + + return c, nil +} + +// RemoveContainer removes a container simply by name. +func (l *Location) RemoveContainer(id string) error { + + if err := l.client.Buckets.Delete(id).Do(); err != nil { + return err + } + + return nil +} + +// ItemByURL retrieves a stow.Item by parsing the URL, in this +// case an item is an object. +func (l *Location) ItemByURL(url *url.URL) (stow.Item, error) { + + if url.Scheme != Kind { + return nil, errors.New("not valid google storage URL") + } + + // /download/storage/v1/b/stowtesttoudhratik/o/a_first%2Fthe%20item + pieces := strings.SplitN(url.Path, "/", 8) + + c, err := l.Container(pieces[5]) + if err != nil { + return nil, stow.ErrNotFound + } + + i, err := c.Item(pieces[7]) + if err != nil { + return nil, stow.ErrNotFound + } + + return i, nil +} diff --git a/vendor/github.com/graymeta/stow/local/container.go b/vendor/github.com/graymeta/stow/local/container.go new file mode 100644 index 000000000..f2e3f8d47 --- /dev/null +++ b/vendor/github.com/graymeta/stow/local/container.go @@ -0,0 +1,173 @@ +package local + +import ( + "errors" + "io" + "net/url" + "os" + "path/filepath" + "strings" + + "github.com/graymeta/stow" +) + +type container struct { + name string + path string +} + +func (c *container) ID() string { + return c.path +} + +func (c *container) Name() string { + return c.name +} + +func (c *container) URL() *url.URL { + return &url.URL{ + Scheme: "file", + Path: filepath.Clean(c.path), + } +} + +func (c *container) CreateItem(name string) (stow.Item, io.WriteCloser, error) { + path := filepath.Join(c.path, name) + item := &item{ + path: path, + } + f, err := os.Create(path) + if err != nil { + return nil, nil, err + } + return item, f, nil +} + +func (c *container) RemoveItem(id string) error { + return os.Remove(id) +} + +func (c *container) Put(name string, r io.Reader, size int64, metadata map[string]interface{}) (stow.Item, error) { + if len(metadata) > 0 { + return nil, stow.NotSupported("metadata") + } + + path := filepath.Join(c.path, name) + item := &item{ + path: path, + } + err := os.MkdirAll(filepath.Dir(path), 0777) + if err != nil { + return nil, err + } + f, err := os.Create(path) + if err != nil { + return nil, err + } + defer f.Close() + n, err := io.Copy(f, r) + if err != nil { + return nil, err + } + if n != size { + return nil, errors.New("bad size") + } + return item, nil +} + +func (c *container) Items(prefix, cursor string, count int) ([]stow.Item, string, error) { + files, err := flatdirs(c.path) + if err != nil { + return nil, "", err + } + if cursor != stow.CursorStart { + // seek to the cursor + ok := false + for i, file := range files { + if file.Name() == cursor { + files = files[i:] + ok = true + break + } + } + if !ok { + return nil, "", stow.ErrBadCursor + } + } + if len(files) > count { + cursor = files[count].Name() + files = files[:count] + } else if len(files) <= count { + cursor = "" // end + } + var items []stow.Item + for _, f := range files { + if f.IsDir() { + continue + } + path, err := filepath.Abs(filepath.Join(c.path, f.Name())) + if err != nil { + return nil, "", err + } + if !strings.HasPrefix(f.Name(), prefix) { + continue + } + item := &item{ + path: path, + } + items = append(items, item) + } + return items, cursor, nil +} + +func (c *container) Item(id string) (stow.Item, error) { + path := id + info, err := os.Stat(path) + if os.IsNotExist(err) { + return nil, stow.ErrNotFound + } + if info.IsDir() { + return nil, errors.New("unexpected directory") + } + + item := &item{ + path: path, + } + return item, nil +} + +// flatdirs walks the entire tree returning a list of +// os.FileInfo for all items encountered. +func flatdirs(path string) ([]os.FileInfo, error) { + var list []os.FileInfo + err := filepath.Walk(path, func(p string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if info.IsDir() { + return nil + } + flatname, err := filepath.Rel(path, p) + if err != nil { + return err + } + list = append(list, fileinfo{ + FileInfo: info, + name: flatname, + }) + return nil + }) + if err != nil { + return nil, err + } + return list, nil +} + +type fileinfo struct { + os.FileInfo + name string +} + +func (f fileinfo) Name() string { + return f.name +} diff --git a/vendor/github.com/graymeta/stow/local/doc.go b/vendor/github.com/graymeta/stow/local/doc.go new file mode 100644 index 000000000..6fdd6fa4c --- /dev/null +++ b/vendor/github.com/graymeta/stow/local/doc.go @@ -0,0 +1,34 @@ +/* +Package local provides an abstraction of a general filesystem. A Stow Container is a directory, and a Stow Item is a file. + +Credentials + +The only information required in accessing a filesystem via Stow is the path of a directory. + +Usage + +Aside from providing stow.Dial with the correct Kind ("local"), a stow.Config instance is needed. This instance requires an entry with a key of stow.ConfigKeyPath and a value of the path of the directory. + +Location + +There are local.location methods which allow the retrieval of one or more directories (Container or Containers). A stow.Item representation of a file can also be achieved (ItemByURL). + +Additional methods provide capabilities to create and remove directories (CreateContainer, RemoveContainer). + +Container + +Of a directory, methods of local.container allow the retrieval of its name (ID or Name) as well as one or more files (Item or Items) that exist within. + +Additional local.container methods allow the removal of a file (RemoveItem) and the creation of one (Put). + +Item + +Methods of local.Item allow the retrieval of quite detailed information. They are: +- full path (ID) +- base file name (Name) +- size in bytes (Size) +- file metadata (path, inode, directory, permission bits, etc) +- last modified date (ETag for string, LastMod for time.Time) +- content (Open) +*/ +package local diff --git a/vendor/github.com/graymeta/stow/local/filedata_darwin.go b/vendor/github.com/graymeta/stow/local/filedata_darwin.go new file mode 100644 index 000000000..5eafb826f --- /dev/null +++ b/vendor/github.com/graymeta/stow/local/filedata_darwin.go @@ -0,0 +1,78 @@ +package local + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "syscall" + "time" +) + +func getFileMetadata(path string, info os.FileInfo) map[string]interface{} { + + hardlink := false + symlink := false + var linkTarget string + var inodedata interface{} + if inode, err := getInodeinfo(info); err != nil { + inodedata = map[string]interface{}{"error": err.Error()} + } else { + inodedata = inode + if inode.NLink > 1 { + hardlink = true + } + } + if info.Mode()&os.ModeSymlink == os.ModeSymlink { + symlink = true + linkTarget, _ = os.Readlink(path) + } + m := map[string]interface{}{ + MetadataPath: filepath.Clean(path), + MetadataIsDir: info.IsDir(), + MetadataDir: filepath.Dir(path), + MetadataName: info.Name(), + MetadataMode: fmt.Sprintf("%o", info.Mode()), + MetadataModeD: fmt.Sprintf("%v", uint32(info.Mode())), + MetadataPerm: info.Mode().String(), + MetadataINode: inodedata, + MetadataSize: info.Size(), + MetadataIsHardlink: hardlink, + MetadataIsSymlink: symlink, + MetadataLink: linkTarget, + } + + if stat := info.Sys().(*syscall.Stat_t); stat != nil { + m["atime"] = time.Unix(int64(stat.Atimespec.Sec), int64(stat.Atimespec.Nsec)).Format(time.RFC3339Nano) + m["mtime"] = time.Unix(int64(stat.Mtimespec.Sec), int64(stat.Mtimespec.Nsec)).Format(time.RFC3339Nano) + m["uid"] = stat.Uid + m["gid"] = stat.Gid + } + + ext := filepath.Ext(info.Name()) + if len(ext) > 0 { + m["ext"] = ext + } + + return m +} + +type inodeinfo struct { + // NLink is the number of times this file is linked to by + // hardlinks. + NLink uint16 + // Ino is the inode number for the file. + Ino uint64 +} + +func getInodeinfo(fi os.FileInfo) (*inodeinfo, error) { + var statT *syscall.Stat_t + var ok bool + if statT, ok = fi.Sys().(*syscall.Stat_t); !ok { + return nil, errors.New("unable to determine if file is a hardlink (expected syscall.Stat_t)") + } + return &inodeinfo{ + Ino: statT.Ino, + NLink: uint16(statT.Nlink), + }, nil +} diff --git a/vendor/github.com/graymeta/stow/local/filedata_linux.go b/vendor/github.com/graymeta/stow/local/filedata_linux.go new file mode 100644 index 000000000..370e0e8d9 --- /dev/null +++ b/vendor/github.com/graymeta/stow/local/filedata_linux.go @@ -0,0 +1,78 @@ +package local + +import ( + "errors" + "fmt" + "os" + "path/filepath" + "syscall" + "time" +) + +func getFileMetadata(path string, info os.FileInfo) map[string]interface{} { + + hardlink := false + symlink := false + var linkTarget string + var inodedata interface{} + if inode, err := getInodeinfo(info); err != nil { + inodedata = map[string]interface{}{"error": err.Error()} + } else { + inodedata = inode + if inode.NLink > 1 { + hardlink = true + } + } + if info.Mode()&os.ModeSymlink == os.ModeSymlink { + symlink = true + linkTarget, _ = os.Readlink(path) + } + m := map[string]interface{}{ + MetadataPath: filepath.Clean(path), + MetadataIsDir: info.IsDir(), + MetadataDir: filepath.Dir(path), + MetadataName: info.Name(), + MetadataMode: fmt.Sprintf("%o", info.Mode()), + MetadataModeD: fmt.Sprintf("%v", uint32(info.Mode())), + MetadataPerm: info.Mode().String(), + MetadataINode: inodedata, + MetadataSize: info.Size(), + MetadataIsHardlink: hardlink, + MetadataIsSymlink: symlink, + MetadataLink: linkTarget, + } + + if stat := info.Sys().(*syscall.Stat_t); stat != nil { + m["atime"] = time.Unix(int64(stat.Atim.Sec), int64(stat.Atim.Nsec)).Format(time.RFC3339Nano) + m["mtime"] = time.Unix(int64(stat.Mtim.Sec), int64(stat.Mtim.Nsec)).Format(time.RFC3339Nano) + m["uid"] = stat.Uid + m["gid"] = stat.Gid + } + + ext := filepath.Ext(info.Name()) + if len(ext) > 0 { + m["ext"] = ext + } + + return m +} + +type inodeinfo struct { + // NLink is the number of times this file is linked to by + // hardlinks. + NLink uint64 + // Ino is the inode number for the file. + Ino uint64 +} + +func getInodeinfo(fi os.FileInfo) (*inodeinfo, error) { + var statT *syscall.Stat_t + var ok bool + if statT, ok = fi.Sys().(*syscall.Stat_t); !ok { + return nil, errors.New("unable to determine if file is a hardlink (expected syscall.Stat_t)") + } + return &inodeinfo{ + Ino: statT.Ino, + NLink: uint64(statT.Nlink), + }, nil +} diff --git a/vendor/github.com/graymeta/stow/local/filedata_windows.go b/vendor/github.com/graymeta/stow/local/filedata_windows.go new file mode 100644 index 000000000..2fa0f0f62 --- /dev/null +++ b/vendor/github.com/graymeta/stow/local/filedata_windows.go @@ -0,0 +1,42 @@ +package local + +import ( + "fmt" + "os" + "path/filepath" + "syscall" + "time" +) + +func getFileMetadata(path string, info os.FileInfo) map[string]interface{} { + hardlink, symlink, linkTarget := false, false, "" + if info.Mode()&os.ModeSymlink == os.ModeSymlink { + symlink = true + linkTarget, _ = os.Readlink(path) + } + m := map[string]interface{}{ + MetadataPath: filepath.Clean(path), + MetadataIsDir: info.IsDir(), + MetadataDir: filepath.Dir(path), + MetadataName: info.Name(), + MetadataMode: fmt.Sprintf("%o", info.Mode()), + MetadataModeD: fmt.Sprintf("%v", uint32(info.Mode())), + MetadataPerm: info.Mode().String(), + MetadataSize: info.Size(), + MetadataIsSymlink: symlink, + MetadataIsHardlink: hardlink, + MetadataLink: linkTarget, + } + + if stat := info.Sys().(*syscall.Win32FileAttributeData); stat != nil { + m["atime"] = time.Unix(0, stat.LastAccessTime.Nanoseconds()).Format(time.RFC3339Nano) + m["mtime"] = time.Unix(0, stat.LastWriteTime.Nanoseconds()).Format(time.RFC3339Nano) + } + + ext := filepath.Ext(info.Name()) + if len(ext) > 0 { + m["ext"] = ext + } + + return m +} diff --git a/vendor/github.com/graymeta/stow/local/item.go b/vendor/github.com/graymeta/stow/local/item.go new file mode 100644 index 000000000..d89d8482c --- /dev/null +++ b/vendor/github.com/graymeta/stow/local/item.go @@ -0,0 +1,107 @@ +package local + +import ( + "io" + "net/url" + "os" + "path/filepath" + "sync" + "time" +) + +// Metadata constants describe the metadata available +// for a local Item. +const ( + MetadataPath = "path" + MetadataIsDir = "is_dir" + MetadataDir = "dir" + MetadataName = "name" + MetadataMode = "mode" + MetadataModeD = "mode_d" + MetadataPerm = "perm" + MetadataINode = "inode" + MetadataSize = "size" + MetadataIsHardlink = "is_hardlink" + MetadataIsSymlink = "is_symlink" + MetadataLink = "link" +) + +type item struct { + path string + infoOnce sync.Once // protects info + info os.FileInfo + infoErr error + metadata map[string]interface{} +} + +func (i *item) ID() string { + return i.path +} + +func (i *item) Name() string { + return filepath.Base(i.path) +} + +func (i *item) Size() (int64, error) { + err := i.ensureInfo() + if err != nil { + return 0, err + } + return i.info.Size(), nil +} + +func (i *item) URL() *url.URL { + return &url.URL{ + Scheme: "file", + Path: filepath.Clean(i.path), + } +} + +func (i *item) ETag() (string, error) { + err := i.ensureInfo() + if err != nil { + return "", nil + } + return i.info.ModTime().String(), nil +} + +// Open opens the file for reading. +func (i *item) Open() (io.ReadCloser, error) { + return os.Open(i.path) +} + +func (i *item) LastMod() (time.Time, error) { + err := i.ensureInfo() + if err != nil { + return time.Time{}, nil + } + + return i.info.ModTime(), nil +} + +func (i *item) ensureInfo() error { + i.infoOnce.Do(func() { + i.info, i.infoErr = os.Lstat(i.path) // retrieve item file info + + i.infoErr = i.setMetadata(i.info) // merge file and metadata maps + if i.infoErr != nil { + return + } + }) + return i.infoErr +} + +func (i *item) setMetadata(info os.FileInfo) error { + fileMetadata := getFileMetadata(i.path, info) // retrieve file metadata + i.metadata = fileMetadata + return nil +} + +// Metadata gets stat information for the file. +func (i *item) Metadata() (map[string]interface{}, error) { + err := i.ensureInfo() + if err != nil { + return nil, err + } + return i.metadata, nil +} diff --git a/vendor/github.com/graymeta/stow/local/local.go b/vendor/github.com/graymeta/stow/local/local.go new file mode 100644 index 000000000..bd76ac044 --- /dev/null +++ b/vendor/github.com/graymeta/stow/local/local.go @@ -0,0 +1,45 @@ +package local + +import ( + "errors" + "net/url" + "os" + + "github.com/graymeta/stow" +) + +// ConfigKeys are the supported configuration items for +// local storage. +const ( + ConfigKeyPath = "path" +) + +// Kind is the kind of Location this package provides. +const Kind = "local" + +const ( + paramTypeValue = "item" +) + +func init() { + makefn := func(config stow.Config) (stow.Location, error) { + path, ok := config.Config(ConfigKeyPath) + if !ok { + return nil, errors.New("missing path config") + } + info, err := os.Stat(path) + if err != nil { + return nil, err + } + if !info.IsDir() { + return nil, errors.New("path must be directory") + } + return &location{ + config: config, + }, nil + } + kindfn := func(u *url.URL) bool { + return u.Scheme == "file" + } + stow.Register(Kind, makefn, kindfn) +} diff --git a/vendor/github.com/graymeta/stow/local/location.go b/vendor/github.com/graymeta/stow/local/location.go new file mode 100644 index 000000000..74e5a3984 --- /dev/null +++ b/vendor/github.com/graymeta/stow/local/location.go @@ -0,0 +1,150 @@ +package local + +import ( + "errors" + "net/url" + "os" + "path/filepath" + + "github.com/graymeta/stow" +) + +type location struct { + // config is the configuration for this location. + config stow.Config +} + +func (l *location) Close() error { + return nil // nothing to close +} + +func (l *location) ItemByURL(u *url.URL) (stow.Item, error) { + i := &item{} + i.path = u.Path + return i, nil +} + +func (l *location) RemoveContainer(id string) error { + return os.RemoveAll(id) +} + +func (l *location) CreateContainer(name string) (stow.Container, error) { + path, ok := l.config.Config(ConfigKeyPath) + if !ok { + return nil, errors.New("missing " + ConfigKeyPath + " configuration") + } + fullpath := filepath.Join(path, name) + if err := os.Mkdir(fullpath, 0777); err != nil { + return nil, err + } + abspath, err := filepath.Abs(fullpath) + if err != nil { + return nil, err + } + return &container{ + name: name, + path: abspath, + }, nil +} + +func (l *location) Containers(prefix string, cursor string, count int) ([]stow.Container, string, error) { + path, ok := l.config.Config(ConfigKeyPath) + if !ok { + return nil, "", errors.New("missing " + ConfigKeyPath + " configuration") + } + files, err := filepath.Glob(filepath.Join(path, prefix+"*")) + if err != nil { + return nil, "", err + } + + var cs []stow.Container + + if prefix == stow.NoPrefix && cursor == stow.CursorStart { + allContainer := container{ + name: "All", + path: path, + } + + cs = append(cs, &allContainer) + } + + cc, err := l.filesToContainers(path, files...) + if err != nil { + return nil, "", err + } + + cs = append(cs, cc...) + + if cursor != stow.CursorStart { + // seek to the cursor + ok := false + for i, c := range cs { + if c.ID() == cursor { + ok = true + cs = cs[i:] + break + } + } + if !ok { + return nil, "", stow.ErrBadCursor + } + } + if len(cs) > count { + cursor = cs[count].ID() + cs = cs[:count] // limit cs to count + } else if len(cs) <= count { + cursor = "" + } + + return cs, cursor, err +} + +func (l *location) Container(id string) (stow.Container, error) { + path, ok := l.config.Config(ConfigKeyPath) + if !ok { + return nil, errors.New("missing " + ConfigKeyPath + " configuration") + } + containers, err := l.filesToContainers(path, id) + if err != nil { + if os.IsNotExist(err) { + return nil, stow.ErrNotFound + } + return nil, err + } + if len(containers) == 0 { + return nil, stow.ErrNotFound + } + return containers[0], nil +} + +// filesToContainers takes a list of files and turns it into a +// stow.ContainerList. +func (l *location) filesToContainers(root string, files ...string) ([]stow.Container, error) { + cs := make([]stow.Container, 0, len(files)) + for _, f := range files { + info, err := os.Stat(f) + if err != nil { + return nil, err + } + if !info.IsDir() { + continue + } + absroot, err := filepath.Abs(root) + if err != nil { + return nil, err + } + path, err := filepath.Abs(f) + if err != nil { + return nil, err + } + name, err := filepath.Rel(absroot, path) + if err != nil { + return nil, err + } + cs = append(cs, &container{ + name: name, + path: path, + }) + } + return cs, nil +} diff --git a/vendor/github.com/graymeta/stow/s3/README.md b/vendor/github.com/graymeta/stow/s3/README.md new file mode 100644 index 000000000..807216fe0 --- /dev/null +++ b/vendor/github.com/graymeta/stow/s3/README.md @@ -0,0 +1,67 @@ +# S3 Stow Implementation + +Location = Amazon S3 + +Container = Bucket + +Item = File + +Helpful Links: + +`http://docs.aws.amazon.com/sdk-for-go/api/service/s3/#example_S3_ListBuckets` + +--- + +SDK Notes: + +- Metadata of an S3 Object can only be set when the Object is created. + +--- + +Concerns: + +- An AWS account may have credentials which temporarily modifies permissions. This is specified by a token value. This feature is implemented but disabled and added as a TODO. + +--- + +Things to know: + +- Paging for the list of containers doesn't exist yet, this is because there's a hard limit of about 100 containers for every account. + +- A client is required to provide a region. Manipulating buckets that reside within other regions isn't possible. + +--- + +###### Dev Notes + +The init function of every implementation of `stow` must call `stow.Register`. + +`stow.Register` accepts a few things: + +### Kind, a string argument respresenting the name of the location. + +`makefn` a function that accepts any type that conforms to the stow.Config +interface. It first validates the values of the `Config` argument, and then +attempts to use the configuration to create a new client. If successful, An +instance of a data type that conforms to the `stow.Location` interface is +created. This Location should have fields that contain the client and +configuration. + +Further calls in the hierarchy of a Location, Container, and Item depend +on the values of the configuration + the client to send and receive information. + +- `kingmatchfn` a function that ensures that a given URL matches the `Kind` of the type of storage. + +--- + +**stow.Register(kind string, makefn func(Config) (Locaion, error), kindmatchfn func(*url.URL) bool)** + +- Adds `kind` and `makefn` into a map that contains a list of locations. + +- Adds `kind` to a slice that contains all of the different kinds. + +- Adds `kind` as part of an anonymous function which validates the scheme of the url.URL + +Once the `stow.Register` function is completed, a location of the given kind is returned. + +--- diff --git a/vendor/github.com/graymeta/stow/s3/config.go b/vendor/github.com/graymeta/stow/s3/config.go new file mode 100644 index 000000000..c05386d76 --- /dev/null +++ b/vendor/github.com/graymeta/stow/s3/config.go @@ -0,0 +1,132 @@ +package s3 + +import ( + "net/http" + "net/url" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/s3" + "github.com/graymeta/stow" + "github.com/pkg/errors" +) + +// Kind represents the name of the location/storage type. +const Kind = "s3" + +const ( + // ConfigAuthType is an optional argument that defines whether to use an IAM role or access key based auth + ConfigAuthType = "auth_type" + + // ConfigAccessKeyID is one key of a pair of AWS credentials. + ConfigAccessKeyID = "access_key_id" + + // ConfigSecretKey is one key of a pair of AWS credentials. + ConfigSecretKey = "secret_key" + + // ConfigToken is an optional argument which is required when providing + // credentials with temporary access. + // ConfigToken = "token" + + // ConfigRegion represents the region/availability zone of the session. + ConfigRegion = "region" + + // ConfigEndpoint is optional config value for changing s3 endpoint + // used for e.g. minio.io + ConfigEndpoint = "endpoint" +) + +func init() { + + makefn := func(config stow.Config) (stow.Location, error) { + + authType, ok := config.Config(ConfigAuthType) + if !ok || authType == "" { + authType = "accesskey" + } + + if !(authType == "accesskey" || authType == "iam") { + return nil, errors.New("invalid auth_type") + } + + if authType == "accesskey" { + _, ok := config.Config(ConfigAccessKeyID) + if !ok { + return nil, errors.New("missing Access Key ID") + } + + _, ok = config.Config(ConfigSecretKey) + if !ok { + return nil, errors.New("missing Secret Key") + } + } + + _, ok = config.Config(ConfigRegion) + if !ok { + return nil, errors.New("missing Region") + } + + // Create a new client (s3 session) + client, err := newS3Client(config) + if err != nil { + return nil, err + } + + // Create a location with given config and client (s3 session). + loc := &location{ + config: config, + client: client, + } + + return loc, nil + } + + kindfn := func(u *url.URL) bool { + return u.Scheme == Kind + } + + stow.Register(Kind, makefn, kindfn) +} + +// Attempts to create a session based on the information given. +func newS3Client(config stow.Config) (*s3.S3, error) { + authType, _ := config.Config(ConfigAuthType) + accessKeyID, _ := config.Config(ConfigAccessKeyID) + secretKey, _ := config.Config(ConfigSecretKey) + // token, _ := config.Config(ConfigToken) + region, _ := config.Config(ConfigRegion) + + if authType == "" { + authType = "accesskey" + } + + awsConfig := aws.NewConfig(). + WithRegion(region). + WithHTTPClient(http.DefaultClient). + WithMaxRetries(aws.UseServiceDefaultRetries). + WithLogger(aws.NewDefaultLogger()). + WithLogLevel(aws.LogOff). + WithSleepDelay(time.Sleep) + + if authType == "accesskey" { + awsConfig.WithCredentials(credentials.NewStaticCredentials(accessKeyID, secretKey, "")) + } + + endpoint, ok := config.Config(ConfigEndpoint) + if ok { + awsConfig.WithEndpoint(endpoint). + WithDisableSSL(true). + WithS3ForcePathStyle(true) + } + + sess := session.New(awsConfig) + if sess == nil { + return nil, errors.New("creating the S3 session") + } + + s3Client := s3.New(sess) + + return s3Client, nil +} diff --git a/vendor/github.com/graymeta/stow/s3/container.go b/vendor/github.com/graymeta/stow/s3/container.go new file mode 100644 index 000000000..a9dc10daf --- /dev/null +++ b/vendor/github.com/graymeta/stow/s3/container.go @@ -0,0 +1,262 @@ +package s3 + +import ( + "bytes" + "io" + "io/ioutil" + "strings" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/s3" + "github.com/graymeta/stow" + "github.com/pkg/errors" +) + +// Amazon S3 bucket contains a creationdate and a name. +type container struct { + name string // Name is needed to retrieve items. + client *s3.S3 // Client is responsible for performing the requests. + region string // Describes the AWS Availability Zone of the S3 Bucket. +} + +// ID returns a string value which represents the name of the container. +func (c *container) ID() string { + return c.name +} + +// Name returns a string value which represents the name of the container. +func (c *container) Name() string { + return c.name +} + +// Item returns a stow.Item instance of a container based on the +// name of the container and the key representing +func (c *container) Item(id string) (stow.Item, error) { + return c.getItem(id) +} + +// Items sends a request to retrieve a list of items that are prepended with +// the prefix argument. The 'cursor' variable facilitates pagination. +func (c *container) Items(prefix, cursor string, count int) ([]stow.Item, string, error) { + itemLimit := int64(count) + + params := &s3.ListObjectsInput{ + Bucket: aws.String(c.Name()), + Marker: &cursor, + MaxKeys: &itemLimit, + Prefix: &prefix, + } + + response, err := c.client.ListObjects(params) + if err != nil { + return nil, "", errors.Wrap(err, "Items, listing objects") + } + + containerItems := make([]stow.Item, len(response.Contents)) // Allocate space for the Item slice. + + for i, object := range response.Contents { + etag := cleanEtag(*object.ETag) // Copy etag value and remove the strings. + object.ETag = &etag // Assign the value to the object field representing the item. + + containerItems[i] = &item{ + container: c, + client: c.client, + properties: properties{ + ETag: object.ETag, + Key: object.Key, + LastModified: object.LastModified, + Owner: object.Owner, + Size: object.Size, + StorageClass: object.StorageClass, + }, + } + } + + // Create a marker and determine if the list of items to retrieve is complete. + // If not, provide the file name of the last item as the next marker. S3 lists + // its items (S3 Objects) in alphabetical order, so it will receive the item name + // and correctly return the next list of items in subsequent requests. + marker := "" + if *response.IsTruncated { + marker = containerItems[len(containerItems)-1].Name() + } + + return containerItems, marker, nil +} + +func (c *container) RemoveItem(id string) error { + params := &s3.DeleteObjectInput{ + Bucket: aws.String(c.Name()), + Key: aws.String(id), + } + + _, err := c.client.DeleteObject(params) + if err != nil { + return errors.Wrapf(err, "RemoveItem, deleting object %+v", params) + } + return nil +} + +// Put sends a request to upload content to the container. The arguments +// received are the name of the item (S3 Object), a reader representing the +// content, and the size of the file. Many more attributes can be given to the +// file, including metadata. Keeping it simple for now. +func (c *container) Put(name string, r io.Reader, size int64, metadata map[string]interface{}) (stow.Item, error) { + content, err := ioutil.ReadAll(r) + if err != nil { + return nil, errors.Wrap(err, "unable to create or update item, reading content") + } + + // Convert map[string]interface{} to map[string]*string + mdPrepped, err := prepMetadata(metadata) + if err != nil { + return nil, errors.Wrap(err, "unable to create or update item, preparing metadata") + } + + params := &s3.PutObjectInput{ + Bucket: aws.String(c.name), // Required + Key: aws.String(name), // Required + ContentLength: aws.Int64(size), + Body: bytes.NewReader(content), + Metadata: mdPrepped, // map[string]*string + } + + // Only Etag is returned. + response, err := c.client.PutObject(params) + if err != nil { + return nil, errors.Wrap(err, "RemoveItem, deleting object") + } + etag := cleanEtag(*response.ETag) + + // Some fields are empty because this information isn't included in the response. + // May have to involve sending a request if we want more specific information. + // Keeping it simple for now. + // s3.Object info: https://github.com/aws/aws-sdk-go/blob/master/service/s3/api.go#L7092-L7107 + // Response: https://github.com/aws/aws-sdk-go/blob/master/service/s3/api.go#L8193-L8227 + newItem := &item{ + container: c, + client: c.client, + properties: properties{ + ETag: &etag, + Key: &name, + Size: &size, + //LastModified *time.Time + //Owner *s3.Owner + //StorageClass *string + }, + } + + return newItem, nil +} + +// Region returns a string representing the region/availability zone of the container. +func (c *container) Region() string { + return c.region +} + +// A request to retrieve a single item includes information that is more specific than +// a PUT. Instead of doing a request within the PUT, make this method available so that the +// request can be made by the field retrieval methods when necessary. This is the case for +// fields that are left out, such as the object's last modified date. This also needs to be +// done only once since the requested information is retained. +// May be simpler to just stick it in PUT and and do a request every time, please vouch +// for this if so. +func (c *container) getItem(id string) (*item, error) { + params := &s3.GetObjectInput{ + Bucket: aws.String(c.Name()), + Key: aws.String(id), + } + + response, err := c.client.GetObject(params) + if err != nil { + // stow needs ErrNotFound to pass the test but amazon returns an opaque error + if strings.Contains(err.Error(), "NoSuchKey") { + return nil, stow.ErrNotFound + } + return nil, errors.Wrap(err, "getItem, getting the object") + } + defer response.Body.Close() + + etag := cleanEtag(*response.ETag) // etag string value contains quotations. Remove them. + md, err := parseMetadata(response.Metadata) + if err != nil { + return nil, errors.Wrap(err, "unable to retrieve Item information, parsing metadata") + } + + i := &item{ + container: c, + client: c.client, + properties: properties{ + ETag: &etag, + Key: &id, + LastModified: response.LastModified, + Owner: nil, // not returned in the response. + Size: response.ContentLength, + StorageClass: response.StorageClass, + Metadata: md, + }, + } + + return i, nil +} + +// Remove quotation marks from beginning and end. This includes quotations that +// are escaped. Also removes leading `W/` from prefix for weak Etags. +// +// Based on the Etag spec, the full etag value () can include: +// - W/"" +// - "" +// - "" +// Source: https://tools.ietf.org/html/rfc7232#section-2.3 +// +// Based on HTTP spec, forward slash is a separator and must be enclosed in +// quotes to be used as a valid value. Hence, the returned value may include: +// - "" +// - \"\" +// Source: https://www.w3.org/Protocols/rfc2616/rfc2616-sec2.html#sec2.2 +// +// This function contains a loop to check for the presence of the three possible +// filler characters and strips them, resulting in only the Etag value. +func cleanEtag(etag string) string { + for { + // Check if the filler characters are present + if strings.HasPrefix(etag, `\"`) { + etag = strings.Trim(etag, `\"`) + + } else if strings.HasPrefix(etag, `"`) { + etag = strings.Trim(etag, `"`) + + } else if strings.HasPrefix(etag, `W/`) { + etag = strings.Replace(etag, `W/`, "", 1) + + } else { + break + } + } + return etag +} + +// prepMetadata parses a raw map into the native type required by S3 to set metadata (map[string]*string). +// TODO: validation for key values. This function also assumes that the value of a key value pair is a string. +func prepMetadata(md map[string]interface{}) (map[string]*string, error) { + m := make(map[string]*string, len(md)) + for key, value := range md { + strValue, valid := value.(string) + if !valid { + return nil, errors.Errorf(`value of key '%s' in metadata must be of type string`, key) + } + m[key] = aws.String(strValue) + } + return m, nil +} + +// The first letter of a dash separated key value is capitalized, so perform a ToLower on it. +// This Key transformation of returning lowercase is consistent with other locations.. +func parseMetadata(md map[string]*string) (map[string]interface{}, error) { + m := make(map[string]interface{}, len(md)) + for key, value := range md { + k := strings.ToLower(key) + m[k] = *value + } + return m, nil +} diff --git a/vendor/github.com/graymeta/stow/s3/doc.go b/vendor/github.com/graymeta/stow/s3/doc.go new file mode 100644 index 000000000..09330a407 --- /dev/null +++ b/vendor/github.com/graymeta/stow/s3/doc.go @@ -0,0 +1,44 @@ +/* +Package s3 provides an abstraction of Amazon S3 (Simple Storage Service). An S3 Bucket is a Stow Container and an S3 Object is a Stow Item. Recall that nested directories exist within S3. + +Usage and Credentials + +There are three separate pieces of information required by Stow to have access to an S3 Stow Location: an AWS User's ACCESS_KEY_ID and SECRET_KEY fields, as well as the physical region of the S3 Endpoint. Ensure that the AWS User whose credentials are used to manipulate the S3 endpoint has permissions to do so. + +stow.Dial requires both a string value ("s3") of the particular Stow Location Kind and a stow.Config instance. The stow.Config instance requires three entries with the specific key value attributes: + +- a key of s3.ConfigAccessKeyID with a value of the AWS account's Access Key ID +- a key of s3.ConfigSecretKey with a value of the AWS account's Secret Key +- a key of s3.ConfigRegion with a value of the S3 endpoint's region (in all lowercase) + +Location + +The s3.location methods allow the retrieval of an S3 endpoint's Bucket or list of Buckets (Container or Containers). A stow.Item representation of an S3 Object can also be retrieved based on the Object's URL (ItemByURL). + +Additional s3.location methods provide capabilities to create and remove S3 Buckets (CreateContainer or RemoveContainer, respectively). + +Container + +There are s3.container methods which can retrieve an S3 Bucket's: + +- name (ID or Name) +- Object or complete list of Objects (Item or Items) +- region + +Additional s3.container methods give Stow the ability to: + +- remove an S3 Bucket (RemoveItem) +- update or create an S3 Object (Put) + +Item + +Methods within an s3.item allow the retrieval of an S3 Object's: +- name (ID or name) +- URL (ItemByUrl) +- size in bytes (Size) +- S3 specific metadata (Metadata, key value pairs usually found within the console) +- last modified date (LastMod) +- Etag (Etag) +- content (Open) +*/ +package s3 diff --git a/vendor/github.com/graymeta/stow/s3/item.go b/vendor/github.com/graymeta/stow/s3/item.go new file mode 100644 index 000000000..1dc5ca8a9 --- /dev/null +++ b/vendor/github.com/graymeta/stow/s3/item.go @@ -0,0 +1,148 @@ +package s3 + +import ( + "io" + "net/url" + "strings" + "sync" + "time" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/s3" + "github.com/graymeta/stow" + "github.com/pkg/errors" +) + +// The item struct contains an id (also the name of the file/S3 Object/Item), +// a container which it belongs to (s3 Bucket), a client, and a URL. The last +// field, properties, contains information about the item, including the ETag, +// file name/id, size, owner, last modified date, and storage class. +// see Object type at http://docs.aws.amazon.com/sdk-for-go/api/service/s3/ +// for more info. +// All fields are unexported because methods exist to facilitate retrieval. +type item struct { + // Container information is required by a few methods. + container *container + // A client is needed to make requests. + client *s3.S3 + // properties represent the characteristics of the file. Name, Etag, etc. + properties properties + infoOnce sync.Once + infoErr error +} + +type properties struct { + ETag *string `type:"string"` + Key *string `min:"1" type:"string"` + LastModified *time.Time `type:"timestamp" timestampFormat:"iso8601"` + Owner *s3.Owner `type:"structure"` + Size *int64 `type:"integer"` + StorageClass *string `type:"string" enum:"ObjectStorageClass"` + Metadata map[string]interface{} +} + +// ID returns a string value that represents the name of a file. +func (i *item) ID() string { + return *i.properties.Key +} + +// Name returns a string value that represents the name of the file. +func (i *item) Name() string { + return *i.properties.Key +} + +// Size returns the size of an item in bytes. +func (i *item) Size() (int64, error) { + return *i.properties.Size, nil +} + +// URL returns a formatted string which follows the predefined format +// that every S3 asset is given. +func (i *item) URL() *url.URL { + genericURL := []string{"https://s3-", i.container.Region(), ".amazonaws.com/", + i.container.Name(), "/", i.Name()} + + return &url.URL{ + Scheme: "s3", + Path: strings.Join(genericURL, ""), + } +} + +// Open retrieves specic information about an item based on the container name +// and path of the file within the container. This response includes the body of +// resource which is returned along with an error. +func (i *item) Open() (io.ReadCloser, error) { + params := &s3.GetObjectInput{ + Bucket: aws.String(i.container.Name()), + Key: aws.String(i.ID()), + } + + response, err := i.client.GetObject(params) + if err != nil { + return nil, errors.Wrap(err, "Open, getting the object") + } + return response.Body, nil +} + +// LastMod returns the last modified date of the item. The response of an item that is PUT +// does not contain this field. Solution? Detect when the LastModified field (a *time.Time) +// is nil, then do a manual request for it via the Item() method of the container which +// does return the specified field. This more detailed information is kept so that we +// won't have to do it again. +func (i *item) LastMod() (time.Time, error) { + err := i.ensureInfo() + if err != nil { + return time.Time{}, errors.Wrap(err, "retrieving Last Modified information of Item") + } + return *i.properties.LastModified, nil +} + +// ETag returns the ETag value from the properies field of an item. +func (i *item) ETag() (string, error) { + return *(i.properties.ETag), nil +} + +func (i *item) Metadata() (map[string]interface{}, error) { + err := i.ensureInfo() + if err != nil { + return nil, errors.Wrap(err, "retrieving metadata") + } + return i.properties.Metadata, nil +} + +func (i *item) ensureInfo() error { + if i.properties.Metadata == nil || i.properties.LastModified == nil { + i.infoOnce.Do(func() { + // Retrieve Item information + itemInfo, infoErr := i.getInfo() + if infoErr != nil { + i.infoErr = infoErr + return + } + + // Set metadata field + i.properties.Metadata, infoErr = itemInfo.Metadata() + if infoErr != nil { + i.infoErr = infoErr + return + } + + // Set LastModified field + lmValue, infoErr := itemInfo.LastMod() + if infoErr != nil { + i.infoErr = infoErr + return + } + i.properties.LastModified = &lmValue + }) + } + return i.infoErr +} + +func (i *item) getInfo() (stow.Item, error) { + itemInfo, err := i.container.getItem(i.ID()) + if err != nil { + return nil, err + } + return itemInfo, nil +} diff --git a/vendor/github.com/graymeta/stow/s3/location.go b/vendor/github.com/graymeta/stow/s3/location.go new file mode 100644 index 000000000..9de80cd47 --- /dev/null +++ b/vendor/github.com/graymeta/stow/s3/location.go @@ -0,0 +1,192 @@ +package s3 + +import ( + "net/url" + "strings" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/service/s3" + "github.com/graymeta/stow" + "github.com/pkg/errors" +) + +// A location contains a client + the configurations used to create the client. +type location struct { + config stow.Config + client *s3.S3 +} + +// CreateContainer creates a new container, in this case an S3 bucket. +// The bare minimum needed is a container name, but there are many other +// options that can be provided. +func (l *location) CreateContainer(containerName string) (stow.Container, error) { + createBucketParams := &s3.CreateBucketInput{ + Bucket: aws.String(containerName), // required + } + + _, err := l.client.CreateBucket(createBucketParams) + if err != nil { + return nil, errors.Wrap(err, "CreateContainer, creating the bucket") + } + + region, _ := l.config.Config("region") + + newContainer := &container{ + name: containerName, + client: l.client, + region: region, + } + + return newContainer, nil +} + +// Containers returns a slice of the Container interface, a cursor, and an error. +// This doesn't seem to exist yet in the API without doing a ton of manual work. +// Get the list of buckets, query every single one to retrieve region info, and finally +// return the list of containers that have a matching region against the client. It's not +// possible to manipulate a container within a region that doesn't match the clients'. +// This is because AWS user credentials can be tied to regions. One solution would be +// to start a new client for every single container where the region matches, this would +// also check the credentials on every new instance... Tabled for later. +func (l *location) Containers(prefix, cursor string, count int) ([]stow.Container, string, error) { + var params *s3.ListBucketsInput + + var containers []stow.Container + + // Response returns exported Owner(*s3.Owner) and Bucket(*s3.[]Bucket) + bucketList, err := l.client.ListBuckets(params) + if err != nil { + return nil, "", errors.Wrap(err, "Containers, listing the buckets") + } + + // Iterate through the slice of pointers to buckets + for _, bucket := range bucketList.Buckets { + // Retrieve region information. + bucketLocParams := &s3.GetBucketLocationInput{ + Bucket: aws.String(*bucket.Name), + } + + // Buckets with region 'US Standard' return nothing. + bucketLocResponse, err := l.client.GetBucketLocation(bucketLocParams) + if err != nil { + return nil, "", errors.Wrap(err, "Containers, getting the bucket location") + } + + clientRegion, _ := l.config.Config("region") + containerRegion := bucketLocResponse.LocationConstraint + + // If containerRegion (* string) is nil, the region is US Standard, which is "us-east-1" + // by default. + if containerRegion == nil { + usStandardRegion := "us-east-1" + containerRegion = &usStandardRegion + } + + // Add buckets with 'US Standard' region. The containerRegion, a pointer, will return nil. + // Also add buckets that have the same region as the client, otherwise continue on. + // The second condition ensures that the bucket contains the given prefix. + if *containerRegion != clientRegion || !strings.HasPrefix(*bucket.Name, prefix) { + continue + } + + newContainer := &container{ + name: *(bucket.Name), + client: l.client, + region: clientRegion, + } + + containers = append(containers, newContainer) + } + + return containers, "", nil +} + +// Close simply satisfies the Location interface. There's nothing that +// needs to be done in order to satisfy the interface. +func (l *location) Close() error { + return nil // nothing to close +} + +// Container retrieves a stow.Container based on its name which must be +// exact. +func (l *location) Container(id string) (stow.Container, error) { + params := &s3.GetBucketLocationInput{ + Bucket: aws.String(id), // Required + } + + _, err := l.client.GetBucketLocation(params) + if err != nil { + // stow needs ErrNotFound to pass the test but amazon returns an opaque error + if strings.Contains(err.Error(), "NoSuchBucket") { + return nil, stow.ErrNotFound + } + return nil, errors.Wrap(err, "Container, getting the bucket location") + } + + region, _ := l.config.Config("region") + + c := &container{ + name: id, + client: l.client, + region: region, + } + + return c, nil +} + +// RemoveContainer removes a container simply by name. +func (l *location) RemoveContainer(id string) error { + params := &s3.DeleteBucketInput{ + Bucket: aws.String(id), + } + + _, err := l.client.DeleteBucket(params) + if err != nil { + return errors.Wrap(err, "RemoveContainer, deleting the bucket") + } + + return nil +} + +// ItemByURL retrieves a stow.Item by parsing the URL, in this +// case an item is an object. +func (l *location) ItemByURL(url *url.URL) (stow.Item, error) { + genericURL := []string{"https://s3-", ".amazonaws.com/"} + + // Remove genericURL[0] from URL: + // url = + firstCut := strings.Replace(url.Path, genericURL[0], "", 1) + + // find first dot so that we could extract region. + dotIndex := strings.Index(firstCut, ".") + + // region of the s3 bucket. + region := firstCut[0:dotIndex] + + // Remove from + // + secondCut := strings.Replace(firstCut, region+genericURL[1], "", 1) + + // Get the index of the first slash to get the end of the bucket name. + firstSlash := strings.Index(secondCut, "/") + + // Grab bucket name + bucketName := secondCut[:firstSlash] + + // Everything afterwards pertains to object. + objectPath := secondCut[firstSlash+1:] + + // Get the container by bucket name. + cont, err := l.Container(bucketName) + if err != nil { + return nil, errors.Wrapf(err, "ItemByURL, getting container by the bucketname %v", bucketName) + } + + // Get the item by object name. + it, err := cont.Item(objectPath) + if err != nil { + return nil, errors.Wrapf(err, "ItemByURL, getting item by object name %v", objectPath) + } + + return it, err +} diff --git a/vendor/github.com/graymeta/stow/stow-aeroplane.png b/vendor/github.com/graymeta/stow/stow-aeroplane.png new file mode 100644 index 0000000000000000000000000000000000000000..023c608551622a9970544389d0d8e01102c26695 GIT binary patch literal 41735 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 literal 0 HcmV?d00001 diff --git a/vendor/github.com/graymeta/stow/stow-definition.png b/vendor/github.com/graymeta/stow/stow-definition.png new file mode 100644 index 0000000000000000000000000000000000000000..017bf1b4acc082f340c7798b3043009a43487294 GIT binary patch literal 35143 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 literal 0 HcmV?d00001 diff --git a/vendor/github.com/graymeta/stow/stow.go b/vendor/github.com/graymeta/stow/stow.go new file mode 100644 index 000000000..16d7af190 --- /dev/null +++ b/vendor/github.com/graymeta/stow/stow.go @@ -0,0 +1,233 @@ +package stow + +import ( + "errors" + "io" + "net/url" + "sync" + "time" +) + +var ( + lock sync.RWMutex // protects locations, kinds and kindmatches + // kinds holds a list of location kinds. + kinds = []string{} + // locations is a map of installed location providers, + // supplying a function that creates a new instance of + // that Location. + locations = map[string]func(Config) (Location, error){} + // kindmatches is a slice of functions that take turns + // trying to match the kind of Location for a given + // URL. Functions return an empty string if it does not + // match. + kindmatches []func(*url.URL) string +) + +var ( + // ErrNotFound is returned when something could not be found. + ErrNotFound = errors.New("not found") + // ErrBadCursor is returned by paging methods when the specified + // cursor is invalid. + ErrBadCursor = errors.New("bad cursor") +) + +var ( + // CursorStart is a string representing a cursor pointing + // to the first page of items or containers. + CursorStart = "" + + // NoPrefix is a string representing no prefix. It can be used + // in any function that asks for a prefix value, but where one is + // not appropriate. + NoPrefix = "" +) + +// IsCursorEnd checks whether the cursor indicates there are no +// more items or not. +func IsCursorEnd(cursor string) bool { + return cursor == "" +} + +// Location represents a storage location. +type Location interface { + io.Closer + // CreateContainer creates a new Container with the + // specified name. + CreateContainer(name string) (Container, error) + // Containers gets a page of containers + // with the specified prefix from this Location. + // The specified cursor is a pointer to the start of + // the containers to get. It it obtained from a previous + // call to this method, or should be CursorStart for the + // first page. + // count is the number of items to return per page. + // The returned cursor can be checked with IsCursorEnd to + // decide if there are any more items or not. + Containers(prefix string, cursor string, count int) ([]Container, string, error) + // Container gets the Container with the specified + // identifier. + Container(id string) (Container, error) + // RemoveContainer removes the container with the specified ID. + RemoveContainer(id string) error + // ItemByURL gets an Item at this location with the + // specified URL. + ItemByURL(url *url.URL) (Item, error) +} + +// Container represents a container. +type Container interface { + // ID gets a unique string describing this Container. + ID() string + // Name gets a human-readable name describing this Container. + Name() string + // Item gets an item by its ID. + Item(id string) (Item, error) + // Items gets a page of items with the specified + // prefix for this Container. + // The specified cursor is a pointer to the start of + // the items to get. It it obtained from a previous + // call to this method, or should be CursorStart for the + // first page. + // count is the number of items to return per page. + // The returned cursor can be checked with IsCursorEnd to + // decide if there are any more items or not. + Items(prefix, cursor string, count int) ([]Item, string, error) + // RemoveItem removes the Item with the specified ID. + RemoveItem(id string) error + // Put creates a new Item with the specified name, and contents + // read from the reader. + Put(name string, r io.Reader, size int64, metadata map[string]interface{}) (Item, error) +} + +// Item represents an item inside a Container. +// Such as a file. +type Item interface { + // ID gets a unique string describing this Item. + ID() string + // Name gets a human-readable name describing this Item. + Name() string + // URL gets a URL for this item. + // For example: + // local: file:///path/to/something + // azure: azure://host:port/api/something + // s3: s3://host:post/etc + URL() *url.URL + // Size gets the size of the Item's contents in bytes. + Size() (int64, error) + // Open opens the Item for reading. + // Calling code must close the io.ReadCloser. + Open() (io.ReadCloser, error) + // ETag is a string that is different when the Item is + // different, and the same when the item is the same. + // Usually this is the last modified datetime. + ETag() (string, error) + // LastMod returns the last modified date of the file. + LastMod() (time.Time, error) + // Metadata gets a map of key/values that belong + // to this Item. + Metadata() (map[string]interface{}, error) +} + +// Config represents key/value configuration. +type Config interface { + // Config gets a string configuration value and a + // bool indicating whether the value was present or not. + Config(name string) (string, bool) +} + +// Register adds a Location implementation, with two helper functions. +// makefn should make a Location with the given Config. +// kindmatchfn should inspect a URL and return whether it represents a Location +// of this kind or not. Code can call KindByURL to get a kind string +// for any given URL and all registered implementations will be consulted. +// Register is usually called in an implementation package's init method. +func Register(kind string, makefn func(Config) (Location, error), kindmatchfn func(*url.URL) bool) { + lock.Lock() + defer lock.Unlock() + // if already registered, leave + if _, ok := locations[kind]; ok { + return + } + locations[kind] = makefn + kinds = append(kinds, kind) + kindmatches = append(kindmatches, func(u *url.URL) string { + if kindmatchfn(u) { + return kind // match + } + return "" // empty string means no match + }) +} + +// Dial gets a new Location with the given kind and +// configuration. +func Dial(kind string, config Config) (Location, error) { + fn, ok := locations[kind] + if !ok { + return nil, errUnknownKind(kind) + } + return fn(config) +} + +// Kinds gets a list of installed location kinds. +func Kinds() []string { + lock.RLock() + defer lock.RUnlock() + return kinds +} + +// KindByURL gets the kind represented by the given URL. +// It consults all registered locations. +// Error returned if no match is found. +func KindByURL(u *url.URL) (string, error) { + lock.RLock() + defer lock.RUnlock() + for _, fn := range kindmatches { + kind := fn(u) + if kind == "" { + continue + } + return kind, nil + } + return "", errUnknownKind("") +} + +// ConfigMap is a map[string]string that implements +// the Config method. +type ConfigMap map[string]string + +// Config gets a string configuration value and a +// bool indicating whether the value was present or not. +func (c ConfigMap) Config(name string) (string, bool) { + val, ok := c[name] + return val, ok +} + +// errUnknownKind indicates that a kind is unknown. +type errUnknownKind string + +func (e errUnknownKind) Error() string { + s := string(e) + if len(s) > 0 { + return "stow: unknown kind \"" + string(e) + "\"" + } + return "stow: unknown kind" +} + +type errNotSupported string + +func (e errNotSupported) Error() string { + return "not supported: " + string(e) +} + +// IsNotSupported gets whether the error is due to +// a feature not being supported by a specific implementation. +func IsNotSupported(err error) bool { + _, ok := err.(errNotSupported) + return ok +} + +// NotSupported gets an error describing the feature +// as not supported by this implementation. +func NotSupported(feature string) error { + return errNotSupported(feature) +} diff --git a/vendor/github.com/graymeta/stow/walk.go b/vendor/github.com/graymeta/stow/walk.go new file mode 100644 index 000000000..3cded5048 --- /dev/null +++ b/vendor/github.com/graymeta/stow/walk.go @@ -0,0 +1,81 @@ +package stow + +// DEV NOTE: tests for this are in test/test.go + +// WalkFunc is a function called for each Item visited +// by Walk. +// If there was a problem, the incoming error will describe +// the problem and the function can decide how to handle +// that error. +// If an error is returned, processing stops. +type WalkFunc func(item Item, err error) error + +// Walk walks all Items in the Container. +// Returns the first error returned by the WalkFunc or +// nil if no errors were returned. +// The pageSize is the number of Items to get per request. +func Walk(container Container, prefix string, pageSize int, fn WalkFunc) error { + var ( + err error + items []Item + cursor = CursorStart + ) + for { + items, cursor, err = container.Items(prefix, cursor, pageSize) + if err != nil { + err = fn(nil, err) + if err != nil { + return err + } + } + for _, item := range items { + err = fn(item, nil) + if err != nil { + return err + } + } + if IsCursorEnd(cursor) { + break + } + } + return nil +} + +// WalkContainersFunc is a function called for each Container visited +// by WalkContainers. +// If there was a problem, the incoming error will describe +// the problem and the function can decide how to handle +// that error. +// If an error is returned, processing stops. +type WalkContainersFunc func(container Container, err error) error + +// WalkContainers walks all Containers in the Location. +// Returns the first error returned by the WalkContainersFunc or +// nil if no errors were returned. +// The pageSize is the number of Containers to get per request. +func WalkContainers(location Location, prefix string, pageSize int, fn WalkContainersFunc) error { + var ( + err error + containers []Container + cursor = CursorStart + ) + for { + containers, cursor, err = location.Containers(prefix, cursor, pageSize) + if err != nil { + err = fn(nil, err) + if err != nil { + return err + } + } + for _, container := range containers { + err = fn(container, nil) + if err != nil { + return err + } + } + if IsCursorEnd(cursor) { + break + } + } + return nil +} diff --git a/vendor/golang.org/x/crypto/scrypt/scrypt.go b/vendor/golang.org/x/crypto/scrypt/scrypt.go new file mode 100644 index 000000000..7455395cf --- /dev/null +++ b/vendor/golang.org/x/crypto/scrypt/scrypt.go @@ -0,0 +1,243 @@ +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Package scrypt implements the scrypt key derivation function as defined in +// Colin Percival's paper "Stronger Key Derivation via Sequential Memory-Hard +// Functions" (http://www.tarsnap.com/scrypt/scrypt.pdf). +package scrypt // import "golang.org/x/crypto/scrypt" + +import ( + "crypto/sha256" + "errors" + + "golang.org/x/crypto/pbkdf2" +) + +const maxInt = int(^uint(0) >> 1) + +// blockCopy copies n numbers from src into dst. +func blockCopy(dst, src []uint32, n int) { + copy(dst, src[:n]) +} + +// blockXOR XORs numbers from dst with n numbers from src. +func blockXOR(dst, src []uint32, n int) { + for i, v := range src[:n] { + dst[i] ^= v + } +} + +// salsaXOR applies Salsa20/8 to the XOR of 16 numbers from tmp and in, +// and puts the result into both both tmp and out. +func salsaXOR(tmp *[16]uint32, in, out []uint32) { + w0 := tmp[0] ^ in[0] + w1 := tmp[1] ^ in[1] + w2 := tmp[2] ^ in[2] + w3 := tmp[3] ^ in[3] + w4 := tmp[4] ^ in[4] + w5 := tmp[5] ^ in[5] + w6 := tmp[6] ^ in[6] + w7 := tmp[7] ^ in[7] + w8 := tmp[8] ^ in[8] + w9 := tmp[9] ^ in[9] + w10 := tmp[10] ^ in[10] + w11 := tmp[11] ^ in[11] + w12 := tmp[12] ^ in[12] + w13 := tmp[13] ^ in[13] + w14 := tmp[14] ^ in[14] + w15 := tmp[15] ^ in[15] + + x0, x1, x2, x3, x4, x5, x6, x7, x8 := w0, w1, w2, w3, w4, w5, w6, w7, w8 + x9, x10, x11, x12, x13, x14, x15 := w9, w10, w11, w12, w13, w14, w15 + + for i := 0; i < 8; i += 2 { + u := x0 + x12 + x4 ^= u<<7 | u>>(32-7) + u = x4 + x0 + x8 ^= u<<9 | u>>(32-9) + u = x8 + x4 + x12 ^= u<<13 | u>>(32-13) + u = x12 + x8 + x0 ^= u<<18 | u>>(32-18) + + u = x5 + x1 + x9 ^= u<<7 | u>>(32-7) + u = x9 + x5 + x13 ^= u<<9 | u>>(32-9) + u = x13 + x9 + x1 ^= u<<13 | u>>(32-13) + u = x1 + x13 + x5 ^= u<<18 | u>>(32-18) + + u = x10 + x6 + x14 ^= u<<7 | u>>(32-7) + u = x14 + x10 + x2 ^= u<<9 | u>>(32-9) + u = x2 + x14 + x6 ^= u<<13 | u>>(32-13) + u = x6 + x2 + x10 ^= u<<18 | u>>(32-18) + + u = x15 + x11 + x3 ^= u<<7 | u>>(32-7) + u = x3 + x15 + x7 ^= u<<9 | u>>(32-9) + u = x7 + x3 + x11 ^= u<<13 | u>>(32-13) + u = x11 + x7 + x15 ^= u<<18 | u>>(32-18) + + u = x0 + x3 + x1 ^= u<<7 | u>>(32-7) + u = x1 + x0 + x2 ^= u<<9 | u>>(32-9) + u = x2 + x1 + x3 ^= u<<13 | u>>(32-13) + u = x3 + x2 + x0 ^= u<<18 | u>>(32-18) + + u = x5 + x4 + x6 ^= u<<7 | u>>(32-7) + u = x6 + x5 + x7 ^= u<<9 | u>>(32-9) + u = x7 + x6 + x4 ^= u<<13 | u>>(32-13) + u = x4 + x7 + x5 ^= u<<18 | u>>(32-18) + + u = x10 + x9 + x11 ^= u<<7 | u>>(32-7) + u = x11 + x10 + x8 ^= u<<9 | u>>(32-9) + u = x8 + x11 + x9 ^= u<<13 | u>>(32-13) + u = x9 + x8 + x10 ^= u<<18 | u>>(32-18) + + u = x15 + x14 + x12 ^= u<<7 | u>>(32-7) + u = x12 + x15 + x13 ^= u<<9 | u>>(32-9) + u = x13 + x12 + x14 ^= u<<13 | u>>(32-13) + u = x14 + x13 + x15 ^= u<<18 | u>>(32-18) + } + x0 += w0 + x1 += w1 + x2 += w2 + x3 += w3 + x4 += w4 + x5 += w5 + x6 += w6 + x7 += w7 + x8 += w8 + x9 += w9 + x10 += w10 + x11 += w11 + x12 += w12 + x13 += w13 + x14 += w14 + x15 += w15 + + out[0], tmp[0] = x0, x0 + out[1], tmp[1] = x1, x1 + out[2], tmp[2] = x2, x2 + out[3], tmp[3] = x3, x3 + out[4], tmp[4] = x4, x4 + out[5], tmp[5] = x5, x5 + out[6], tmp[6] = x6, x6 + out[7], tmp[7] = x7, x7 + out[8], tmp[8] = x8, x8 + out[9], tmp[9] = x9, x9 + out[10], tmp[10] = x10, x10 + out[11], tmp[11] = x11, x11 + out[12], tmp[12] = x12, x12 + out[13], tmp[13] = x13, x13 + out[14], tmp[14] = x14, x14 + out[15], tmp[15] = x15, x15 +} + +func blockMix(tmp *[16]uint32, in, out []uint32, r int) { + blockCopy(tmp[:], in[(2*r-1)*16:], 16) + for i := 0; i < 2*r; i += 2 { + salsaXOR(tmp, in[i*16:], out[i*8:]) + salsaXOR(tmp, in[i*16+16:], out[i*8+r*16:]) + } +} + +func integer(b []uint32, r int) uint64 { + j := (2*r - 1) * 16 + return uint64(b[j]) | uint64(b[j+1])<<32 +} + +func smix(b []byte, r, N int, v, xy []uint32) { + var tmp [16]uint32 + x := xy + y := xy[32*r:] + + j := 0 + for i := 0; i < 32*r; i++ { + x[i] = uint32(b[j]) | uint32(b[j+1])<<8 | uint32(b[j+2])<<16 | uint32(b[j+3])<<24 + j += 4 + } + for i := 0; i < N; i += 2 { + blockCopy(v[i*(32*r):], x, 32*r) + blockMix(&tmp, x, y, r) + + blockCopy(v[(i+1)*(32*r):], y, 32*r) + blockMix(&tmp, y, x, r) + } + for i := 0; i < N; i += 2 { + j := int(integer(x, r) & uint64(N-1)) + blockXOR(x, v[j*(32*r):], 32*r) + blockMix(&tmp, x, y, r) + + j = int(integer(y, r) & uint64(N-1)) + blockXOR(y, v[j*(32*r):], 32*r) + blockMix(&tmp, y, x, r) + } + j = 0 + for _, v := range x[:32*r] { + b[j+0] = byte(v >> 0) + b[j+1] = byte(v >> 8) + b[j+2] = byte(v >> 16) + b[j+3] = byte(v >> 24) + j += 4 + } +} + +// Key derives a key from the password, salt, and cost parameters, returning +// a byte slice of length keyLen that can be used as cryptographic key. +// +// N is a CPU/memory cost parameter, which must be a power of two greater than 1. +// r and p must satisfy r * p < 2³⁰. If the parameters do not satisfy the +// limits, the function returns a nil byte slice and an error. +// +// For example, you can get a derived key for e.g. AES-256 (which needs a +// 32-byte key) by doing: +// +// dk, err := scrypt.Key([]byte("some password"), salt, 16384, 8, 1, 32) +// +// The recommended parameters for interactive logins as of 2009 are N=16384, +// r=8, p=1. They should be increased as memory latency and CPU parallelism +// increases. Remember to get a good random salt. +func Key(password, salt []byte, N, r, p, keyLen int) ([]byte, error) { + if N <= 1 || N&(N-1) != 0 { + return nil, errors.New("scrypt: N must be > 1 and a power of 2") + } + if uint64(r)*uint64(p) >= 1<<30 || r > maxInt/128/p || r > maxInt/256 || N > maxInt/128/r { + return nil, errors.New("scrypt: parameters are too large") + } + + xy := make([]uint32, 64*r) + v := make([]uint32, 32*N*r) + b := pbkdf2.Key(password, salt, 1, p*128*r, sha256.New) + + for i := 0; i < p; i++ { + smix(b[i*128*r:], r, N, v, xy) + } + + return pbkdf2.Key(password, b, 1, keyLen, sha256.New), nil +}