mirror of
https://github.com/getsops/sops.git
synced 2026-02-05 12:45:21 +01:00
Implement sops publish command (#473)
* Implement `sops publish` command Publishes a file to a pre-configured destination (this lives in the sops config file). Additionally, support re-encryption rules that work just like the creation rules. Initial support for S3/GCS. This is a part of the sops-workspace v2.0 project Includes the addition of a new dependency: github.com/googleapis/gax-go/v2 * code review changes; support global --verbose flag * Switch to recreation_rule with full support Reencryption rule is now recreation rule and supports everything that a creation rule does. Now, when you load a config for a file, you load either the creation rule or the destination rule. I'm not sure about this style long term, but it allows for support to be added for the recreation rules without a bigger refactor of how the config file works. * split loadForFileFromBytes into two functions remove branching based on destination rule or not, create one for creation rules and one for destination rules * pretty diff for keygroup updates in sops publish
This commit is contained in:
25
README.rst
25
README.rst
@@ -791,6 +791,31 @@ By default ``sops`` just dumps all the output to the standard output. We can use
|
||||
Beware using both ``--in-place`` and ``--output`` flags will result in an error.
|
||||
|
||||
|
||||
Using the publish command
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
``sops publish $file`` publishes a file to a pre-configured destination (this lives in the sops
|
||||
config file). Additionally, support re-encryption rules that work just like the creation rules.
|
||||
|
||||
This command requires a ``.sops.yaml`` configuration file. Below is an example:
|
||||
|
||||
.. code:: yaml
|
||||
|
||||
destination_rules:
|
||||
- s3_bucket: "sops-secrets"
|
||||
path_regex: s3/*
|
||||
recreation_rule:
|
||||
pgp: F69E4901EDBAD2D1753F8C67A64535C4163FB307
|
||||
- gcs_bucket: "sops-secrets"
|
||||
path_regex: gcs/*
|
||||
recreation_rule:
|
||||
pgp: F69E4901EDBAD2D1753F8C67A64535C4163FB307
|
||||
|
||||
The above configuration will place all files under ``s3/*`` into the S3 bucket ``sops-secrets`` and
|
||||
will place all files under ``gcs/*`` into the GCS bucket ``sops-secrets``. As well, it will decrypt
|
||||
these files and re-encrypt them using the ``F69E4901EDBAD2D1753F8C67A64535C4163FB307`` pgp key.
|
||||
|
||||
You would deploy a file to S3 with a command like: ``sops publish s3/app.yaml``
|
||||
|
||||
Important information on types
|
||||
------------------------------
|
||||
|
||||
|
||||
@@ -8,9 +8,11 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/fatih/color"
|
||||
wordwrap "github.com/mitchellh/go-wordwrap"
|
||||
"go.mozilla.org/sops"
|
||||
"go.mozilla.org/sops/cmd/sops/codes"
|
||||
"go.mozilla.org/sops/keys"
|
||||
"go.mozilla.org/sops/keyservice"
|
||||
"go.mozilla.org/sops/kms"
|
||||
"go.mozilla.org/sops/stores/dotenv"
|
||||
@@ -339,3 +341,65 @@ func RecoverDataKeyFromBuggyKMS(opts GenericDecryptOpts, tree *sops.Tree) []byte
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type Diff struct {
|
||||
Common []keys.MasterKey
|
||||
Added []keys.MasterKey
|
||||
Removed []keys.MasterKey
|
||||
}
|
||||
|
||||
func max(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func DiffKeyGroups(ours, theirs []sops.KeyGroup) []Diff {
|
||||
var diffs []Diff
|
||||
for i := 0; i < max(len(ours), len(theirs)); i++ {
|
||||
var diff Diff
|
||||
var ourGroup, theirGroup sops.KeyGroup
|
||||
if len(ours) > i {
|
||||
ourGroup = ours[i]
|
||||
}
|
||||
if len(theirs) > i {
|
||||
theirGroup = theirs[i]
|
||||
}
|
||||
ourKeys := make(map[string]struct{})
|
||||
theirKeys := make(map[string]struct{})
|
||||
for _, key := range ourGroup {
|
||||
ourKeys[key.ToString()] = struct{}{}
|
||||
}
|
||||
for _, key := range theirGroup {
|
||||
if _, ok := ourKeys[key.ToString()]; ok {
|
||||
diff.Common = append(diff.Common, key)
|
||||
} else {
|
||||
diff.Added = append(diff.Added, key)
|
||||
}
|
||||
theirKeys[key.ToString()] = struct{}{}
|
||||
}
|
||||
for _, key := range ourGroup {
|
||||
if _, ok := theirKeys[key.ToString()]; !ok {
|
||||
diff.Removed = append(diff.Removed, key)
|
||||
}
|
||||
}
|
||||
diffs = append(diffs, diff)
|
||||
}
|
||||
return diffs
|
||||
}
|
||||
|
||||
func PrettyPrintDiffs(diffs []Diff) {
|
||||
for i, diff := range diffs {
|
||||
color.New(color.Underline).Printf("Group %d\n", i+1)
|
||||
for _, c := range diff.Common {
|
||||
fmt.Printf(" %s\n", c.ToString())
|
||||
}
|
||||
for _, c := range diff.Added {
|
||||
color.New(color.FgGreen).Printf("+++ %s\n", c.ToString())
|
||||
}
|
||||
for _, c := range diff.Removed {
|
||||
color.New(color.FgRed).Printf("--- %s\n", c.ToString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import (
|
||||
"go.mozilla.org/sops/cmd/sops/common"
|
||||
"go.mozilla.org/sops/cmd/sops/subcommand/groups"
|
||||
keyservicecmd "go.mozilla.org/sops/cmd/sops/subcommand/keyservice"
|
||||
publishcmd "go.mozilla.org/sops/cmd/sops/subcommand/publish"
|
||||
"go.mozilla.org/sops/cmd/sops/subcommand/updatekeys"
|
||||
"go.mozilla.org/sops/config"
|
||||
"go.mozilla.org/sops/gcpkms"
|
||||
@@ -105,6 +106,49 @@ func main() {
|
||||
For more information, see the README at github.com/mozilla/sops`
|
||||
app.EnableBashCompletion = true
|
||||
app.Commands = []cli.Command{
|
||||
{
|
||||
Name: "publish",
|
||||
Usage: "Publish sops file to a configured destination",
|
||||
ArgsUsage: `file`,
|
||||
Flags: append([]cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "yes, y",
|
||||
Usage: `pre-approve all changes and run non-interactively`,
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "verbose",
|
||||
Usage: "Enable verbose logging output",
|
||||
},
|
||||
}, keyserviceFlags...),
|
||||
Action: func(c *cli.Context) error {
|
||||
if c.Bool("verbose") || c.GlobalBool("verbose") {
|
||||
logging.SetLevel(logrus.DebugLevel)
|
||||
}
|
||||
configPath, err := config.FindConfigFile(".")
|
||||
if err != nil {
|
||||
return common.NewExitError(err, codes.ErrorGeneric)
|
||||
}
|
||||
if c.NArg() < 1 {
|
||||
return common.NewExitError("Error: no file specified", codes.NoFileSpecified)
|
||||
}
|
||||
fileName := c.Args()[0]
|
||||
inputStore := inputStore(c, fileName)
|
||||
err = publishcmd.Run(publishcmd.Opts{
|
||||
ConfigPath: configPath,
|
||||
InputPath: fileName,
|
||||
InputStore: inputStore,
|
||||
Cipher: aes.NewCipher(),
|
||||
KeyServices: keyservices(c),
|
||||
Interactive: !c.Bool("yes"),
|
||||
})
|
||||
if cliErr, ok := err.(*cli.ExitError); ok && cliErr != nil {
|
||||
return cliErr
|
||||
} else if err != nil {
|
||||
return common.NewExitError(err, codes.ErrorGeneric)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "keyservice",
|
||||
Usage: "start a SOPS key service server",
|
||||
@@ -129,7 +173,7 @@ func main() {
|
||||
},
|
||||
},
|
||||
Action: func(c *cli.Context) error {
|
||||
if c.Bool("verbose") {
|
||||
if c.Bool("verbose") || c.GlobalBool("verbose") {
|
||||
logging.SetLevel(logrus.DebugLevel)
|
||||
}
|
||||
err := keyservicecmd.Run(keyservicecmd.Opts{
|
||||
|
||||
151
cmd/sops/subcommand/publish/publish.go
Normal file
151
cmd/sops/subcommand/publish/publish.go
Normal file
@@ -0,0 +1,151 @@
|
||||
package publish
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"go.mozilla.org/sops"
|
||||
"go.mozilla.org/sops/cmd/sops/codes"
|
||||
"go.mozilla.org/sops/cmd/sops/common"
|
||||
"go.mozilla.org/sops/config"
|
||||
"go.mozilla.org/sops/keyservice"
|
||||
"go.mozilla.org/sops/logging"
|
||||
"go.mozilla.org/sops/version"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var log *logrus.Logger
|
||||
|
||||
func init() {
|
||||
log = logging.NewLogger("PUBLISH")
|
||||
}
|
||||
|
||||
type Opts struct {
|
||||
Interactive bool
|
||||
Cipher sops.Cipher
|
||||
ConfigPath string
|
||||
InputPath string
|
||||
KeyServices []keyservice.KeyServiceClient
|
||||
InputStore sops.Store
|
||||
}
|
||||
|
||||
func Run(opts Opts) error {
|
||||
var fileContents []byte
|
||||
path, err := filepath.Abs(opts.InputPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.IsDir() {
|
||||
return fmt.Errorf("can't operate on a directory")
|
||||
}
|
||||
_, fileName := filepath.Split(path)
|
||||
|
||||
conf, err := config.LoadDestinationRuleForFile(opts.ConfigPath, opts.InputPath, make(map[string]*string))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if conf.Destination == nil {
|
||||
return errors.New("no destination configured for this file")
|
||||
}
|
||||
|
||||
// Check that this is a sops-encrypted file
|
||||
tree, err := common.LoadEncryptedFile(opts.InputStore, opts.InputPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Re-encrypt if settings exist to do so
|
||||
if len(conf.KeyGroups[0]) != 0 {
|
||||
log.Debug("Re-encrypting tree before publishing")
|
||||
_, err = common.DecryptTree(common.DecryptTreeOpts{
|
||||
Cipher: opts.Cipher,
|
||||
IgnoreMac: false,
|
||||
Tree: tree,
|
||||
KeyServices: opts.KeyServices,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
diffs := common.DiffKeyGroups(tree.Metadata.KeyGroups, conf.KeyGroups)
|
||||
keysWillChange := false
|
||||
for _, diff := range diffs {
|
||||
if len(diff.Added) > 0 || len(diff.Removed) > 0 {
|
||||
keysWillChange = true
|
||||
}
|
||||
}
|
||||
if keysWillChange {
|
||||
fmt.Printf("The following changes will be made to the file's key groups:\n")
|
||||
common.PrettyPrintDiffs(diffs)
|
||||
}
|
||||
|
||||
tree.Metadata = sops.Metadata{
|
||||
KeyGroups: conf.KeyGroups,
|
||||
UnencryptedSuffix: conf.UnencryptedSuffix,
|
||||
EncryptedSuffix: conf.EncryptedSuffix,
|
||||
Version: version.Version,
|
||||
ShamirThreshold: conf.ShamirThreshold,
|
||||
}
|
||||
|
||||
dataKey, errs := tree.GenerateDataKeyWithKeyServices(opts.KeyServices)
|
||||
if len(errs) > 0 {
|
||||
err = fmt.Errorf("Could not generate data key: %s", errs)
|
||||
return err
|
||||
}
|
||||
|
||||
err = common.EncryptTree(common.EncryptTreeOpts{
|
||||
DataKey: dataKey,
|
||||
Tree: tree,
|
||||
Cipher: opts.Cipher,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
fileContents, err = opts.InputStore.EmitEncryptedFile(*tree)
|
||||
if err != nil {
|
||||
return common.NewExitError(fmt.Sprintf("Could not marshal tree: %s", err), codes.ErrorDumpingTree)
|
||||
}
|
||||
} else {
|
||||
fileContents, err = ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not read file: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
if opts.Interactive {
|
||||
var response string
|
||||
for response != "y" && response != "n" {
|
||||
fmt.Printf("uploading %s to %s ? (y/n): ", path, conf.Destination.Path(fileName))
|
||||
_, err := fmt.Scanln(&response)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if response == "n" {
|
||||
return errors.New("Publish canceled")
|
||||
}
|
||||
}
|
||||
|
||||
err = conf.Destination.Upload(fileContents, fileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
@@ -6,11 +6,9 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/fatih/color"
|
||||
"go.mozilla.org/sops"
|
||||
"go.mozilla.org/sops/cmd/sops/codes"
|
||||
"go.mozilla.org/sops/cmd/sops/common"
|
||||
"go.mozilla.org/sops/config"
|
||||
"go.mozilla.org/sops/keys"
|
||||
"go.mozilla.org/sops/keyservice"
|
||||
)
|
||||
|
||||
@@ -48,10 +46,11 @@ func updateFile(opts Opts) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
diffs := diffKeyGroups(tree.Metadata.KeyGroups, conf.KeyGroups)
|
||||
|
||||
diffs := common.DiffKeyGroups(tree.Metadata.KeyGroups, conf.KeyGroups)
|
||||
keysWillChange := false
|
||||
for _, diff := range diffs {
|
||||
if len(diff.added) > 0 || len(diff.removed) > 0 {
|
||||
if len(diff.Added) > 0 || len(diff.Removed) > 0 {
|
||||
keysWillChange = true
|
||||
}
|
||||
}
|
||||
@@ -60,18 +59,8 @@ func updateFile(opts Opts) error {
|
||||
return nil
|
||||
}
|
||||
fmt.Printf("The following changes will be made to the file's groups:\n")
|
||||
for i, diff := range diffs {
|
||||
color.New(color.Underline).Printf("Group %d\n", i+1)
|
||||
for _, c := range diff.common {
|
||||
fmt.Printf(" %s\n", c.ToString())
|
||||
}
|
||||
for _, c := range diff.added {
|
||||
color.New(color.FgGreen).Printf("+++ %s\n", c.ToString())
|
||||
}
|
||||
for _, c := range diff.removed {
|
||||
color.New(color.FgRed).Printf("--- %s\n", c.ToString())
|
||||
}
|
||||
}
|
||||
common.PrettyPrintDiffs(diffs)
|
||||
|
||||
if opts.Interactive {
|
||||
var response string
|
||||
for response != "y" && response != "n" {
|
||||
@@ -88,7 +77,7 @@ func updateFile(opts Opts) error {
|
||||
}
|
||||
key, err := tree.Metadata.GetDataKeyWithKeyServices(opts.KeyServices)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error getting data key: %s", err)
|
||||
return common.NewExitError(err, codes.CouldNotRetrieveKey)
|
||||
}
|
||||
tree.Metadata.KeyGroups = conf.KeyGroups
|
||||
if opts.GroupQuorum != 0 {
|
||||
@@ -101,7 +90,7 @@ func updateFile(opts Opts) error {
|
||||
}
|
||||
output, err := store.EmitEncryptedFile(*tree)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error marshaling tree: %s", err)
|
||||
return common.NewExitError(fmt.Sprintf("Could not marshal tree: %s", err), codes.ErrorDumpingTree)
|
||||
}
|
||||
outputFile, err := os.Create(opts.InputPath)
|
||||
if err != nil {
|
||||
@@ -116,56 +105,9 @@ func updateFile(opts Opts) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
type diff struct {
|
||||
common []keys.MasterKey
|
||||
added []keys.MasterKey
|
||||
removed []keys.MasterKey
|
||||
}
|
||||
|
||||
func max(a, b int) int {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func min(a, b int) int {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func diffKeyGroups(ours, theirs []sops.KeyGroup) []diff {
|
||||
var diffs []diff
|
||||
for i := 0; i < max(len(ours), len(theirs)); i++ {
|
||||
var diff diff
|
||||
var ourGroup, theirGroup sops.KeyGroup
|
||||
if len(ours) > i {
|
||||
ourGroup = ours[i]
|
||||
}
|
||||
if len(theirs) > i {
|
||||
theirGroup = theirs[i]
|
||||
}
|
||||
ourKeys := make(map[string]struct{})
|
||||
theirKeys := make(map[string]struct{})
|
||||
for _, key := range ourGroup {
|
||||
ourKeys[key.ToString()] = struct{}{}
|
||||
}
|
||||
for _, key := range theirGroup {
|
||||
if _, ok := ourKeys[key.ToString()]; ok {
|
||||
diff.common = append(diff.common, key)
|
||||
} else {
|
||||
diff.added = append(diff.added, key)
|
||||
}
|
||||
theirKeys[key.ToString()] = struct{}{}
|
||||
}
|
||||
for _, key := range ourGroup {
|
||||
if _, ok := theirKeys[key.ToString()]; !ok {
|
||||
diff.removed = append(diff.removed, key)
|
||||
}
|
||||
}
|
||||
diffs = append(diffs, diff)
|
||||
}
|
||||
return diffs
|
||||
}
|
||||
|
||||
182
config/config.go
182
config/config.go
@@ -18,6 +18,7 @@ import (
|
||||
"go.mozilla.org/sops/kms"
|
||||
"go.mozilla.org/sops/logging"
|
||||
"go.mozilla.org/sops/pgp"
|
||||
"go.mozilla.org/sops/publish"
|
||||
)
|
||||
|
||||
var log *logrus.Logger
|
||||
@@ -61,6 +62,7 @@ func FindConfigFile(start string) (string, error) {
|
||||
|
||||
type configFile struct {
|
||||
CreationRules []creationRule `yaml:"creation_rules"`
|
||||
DestinationRules []destinationRule `yaml:"destination_rules"`
|
||||
}
|
||||
|
||||
type keyGroup struct {
|
||||
@@ -87,8 +89,16 @@ type azureKVKey struct {
|
||||
Version string `yaml:"version"`
|
||||
}
|
||||
|
||||
type destinationRule struct {
|
||||
PathRegex string `yaml:"path_regex"`
|
||||
S3Bucket string `yaml:"s3_bucket"`
|
||||
S3Prefix string `yaml:"s3_prefix"`
|
||||
GCSBucket string `yaml:"gcs_bucket"`
|
||||
GCSPrefix string `yaml:"gcs_prefix"`
|
||||
RecreationRule creationRule `yaml:"recreation_rule,omitempty"`
|
||||
}
|
||||
|
||||
type creationRule struct {
|
||||
FilenameRegex string `yaml:"filename_regex"`
|
||||
PathRegex string `yaml:"path_regex"`
|
||||
KMS string
|
||||
AwsProfile string `yaml:"aws_profile"`
|
||||
@@ -116,50 +126,13 @@ type Config struct {
|
||||
ShamirThreshold int
|
||||
UnencryptedSuffix string
|
||||
EncryptedSuffix string
|
||||
Destination publish.Destination
|
||||
}
|
||||
|
||||
func loadForFileFromBytes(confBytes []byte, filePath string, kmsEncryptionContext map[string]*string) (*Config, error) {
|
||||
conf := configFile{}
|
||||
err := conf.load(confBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error loading config: %s", err)
|
||||
}
|
||||
var rule *creationRule
|
||||
|
||||
for _, r := range conf.CreationRules {
|
||||
if r.PathRegex == "" && r.FilenameRegex == "" {
|
||||
rule = &r
|
||||
break
|
||||
}
|
||||
if r.PathRegex != "" && r.FilenameRegex != "" {
|
||||
return nil, fmt.Errorf("error loading config: both filename_regex and path_regex were found, use only path_regex")
|
||||
}
|
||||
if r.FilenameRegex != "" {
|
||||
if match, _ := regexp.MatchString(r.FilenameRegex, filePath); match {
|
||||
log.Warn("The key: filename_regex will be removed in a future release. Instead use key: path_regex in your .sops.yaml file")
|
||||
rule = &r
|
||||
break
|
||||
}
|
||||
}
|
||||
if r.PathRegex != "" {
|
||||
if match, _ := regexp.MatchString(r.PathRegex, filePath); match {
|
||||
rule = &r
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if rule == nil {
|
||||
return nil, fmt.Errorf("error loading config: no matching creation rules found")
|
||||
}
|
||||
|
||||
if rule.UnencryptedSuffix != "" && rule.EncryptedSuffix != "" {
|
||||
return nil, fmt.Errorf("error loading config: cannot use both encrypted_suffix and unencrypted_suffix for the same rule")
|
||||
}
|
||||
|
||||
func getKeyGroupsFromCreationRule(cRule *creationRule, kmsEncryptionContext map[string]*string) ([]sops.KeyGroup, error) {
|
||||
var groups []sops.KeyGroup
|
||||
if len(rule.KeyGroups) > 0 {
|
||||
for _, group := range rule.KeyGroups {
|
||||
if len(cRule.KeyGroups) > 0 {
|
||||
for _, group := range cRule.KeyGroups {
|
||||
var keyGroup sops.KeyGroup
|
||||
for _, k := range group.PGP {
|
||||
keyGroup = append(keyGroup, pgp.NewMasterKeyFromFingerprint(k))
|
||||
@@ -174,16 +147,16 @@ func loadForFileFromBytes(confBytes []byte, filePath string, kmsEncryptionContex
|
||||
}
|
||||
} else {
|
||||
var keyGroup sops.KeyGroup
|
||||
for _, k := range pgp.MasterKeysFromFingerprintString(rule.PGP) {
|
||||
for _, k := range pgp.MasterKeysFromFingerprintString(cRule.PGP) {
|
||||
keyGroup = append(keyGroup, k)
|
||||
}
|
||||
for _, k := range kms.MasterKeysFromArnString(rule.KMS, kmsEncryptionContext, rule.AwsProfile) {
|
||||
for _, k := range kms.MasterKeysFromArnString(cRule.KMS, kmsEncryptionContext, cRule.AwsProfile) {
|
||||
keyGroup = append(keyGroup, k)
|
||||
}
|
||||
for _, k := range gcpkms.MasterKeysFromResourceIDString(rule.GCPKMS) {
|
||||
for _, k := range gcpkms.MasterKeysFromResourceIDString(cRule.GCPKMS) {
|
||||
keyGroup = append(keyGroup, k)
|
||||
}
|
||||
azureKeys, err := azkv.MasterKeysFromURLs(rule.AzureKeyVault)
|
||||
azureKeys, err := azkv.MasterKeysFromURLs(cRule.AzureKeyVault)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -192,6 +165,32 @@ func loadForFileFromBytes(confBytes []byte, filePath string, kmsEncryptionContex
|
||||
}
|
||||
groups = append(groups, keyGroup)
|
||||
}
|
||||
return groups, nil
|
||||
}
|
||||
|
||||
func loadConfigFile(confPath string) (*configFile, error) {
|
||||
confBytes, err := ioutil.ReadFile(confPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read config file: %s", err)
|
||||
}
|
||||
conf := &configFile{}
|
||||
err = conf.load(confBytes)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error loading config: %s", err)
|
||||
}
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
func configFromRule(rule *creationRule, kmsEncryptionContext map[string]*string) (*Config, error) {
|
||||
if rule.UnencryptedSuffix != "" && rule.EncryptedSuffix != "" {
|
||||
return nil, fmt.Errorf("error loading config: cannot use both encrypted_suffix and unencrypted_suffix for the same rule")
|
||||
}
|
||||
|
||||
groups, err := getKeyGroupsFromCreationRule(rule, kmsEncryptionContext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Config{
|
||||
KeyGroups: groups,
|
||||
ShamirThreshold: rule.ShamirThreshold,
|
||||
@@ -200,13 +199,98 @@ func loadForFileFromBytes(confBytes []byte, filePath string, kmsEncryptionContex
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseDestinationRuleForFile(conf *configFile, filePath string, kmsEncryptionContext map[string]*string) (*Config, error) {
|
||||
var rule *creationRule
|
||||
var dRule *destinationRule
|
||||
|
||||
if len(conf.DestinationRules) > 0 {
|
||||
for _, r := range conf.DestinationRules {
|
||||
if r.PathRegex == "" {
|
||||
dRule = &r
|
||||
rule = &dRule.RecreationRule
|
||||
break
|
||||
}
|
||||
if r.PathRegex != "" {
|
||||
if match, _ := regexp.MatchString(r.PathRegex, filePath); match {
|
||||
dRule = &r
|
||||
rule = &dRule.RecreationRule
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if dRule == nil {
|
||||
return nil, fmt.Errorf("error loading config: no matching destination found in config")
|
||||
}
|
||||
|
||||
var dest publish.Destination
|
||||
if dRule != nil {
|
||||
if dRule.S3Bucket != "" && dRule.GCSBucket != "" {
|
||||
return nil, fmt.Errorf("error loading config: both s3_bucket and gcs_bucket were found for destination rule, you can only use one.")
|
||||
}
|
||||
if dRule.S3Bucket != "" {
|
||||
dest = publish.NewS3Destination(dRule.S3Bucket, dRule.S3Prefix)
|
||||
}
|
||||
if dRule.GCSBucket != "" {
|
||||
dest = publish.NewGCSDestination(dRule.GCSBucket, dRule.GCSPrefix)
|
||||
}
|
||||
}
|
||||
|
||||
config, err := configFromRule(rule, kmsEncryptionContext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
config.Destination = dest
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func parseCreationRuleForFile(conf *configFile, filePath string, kmsEncryptionContext map[string]*string) (*Config, error) {
|
||||
var rule *creationRule
|
||||
|
||||
for _, r := range conf.CreationRules {
|
||||
if r.PathRegex == "" {
|
||||
rule = &r
|
||||
break
|
||||
}
|
||||
if r.PathRegex != "" {
|
||||
if match, _ := regexp.MatchString(r.PathRegex, filePath); match {
|
||||
rule = &r
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if rule == nil {
|
||||
return nil, fmt.Errorf("error loading config: no matching creation rules found")
|
||||
}
|
||||
|
||||
config, err := configFromRule(rule, kmsEncryptionContext)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// LoadForFile load the configuration for a given SOPS file from the config file at confPath. A kmsEncryptionContext
|
||||
// should be provided for configurations that do not contain key groups, as there's no way to specify context inside
|
||||
// a SOPS config file outside of key groups.
|
||||
func LoadForFile(confPath string, filePath string, kmsEncryptionContext map[string]*string) (*Config, error) {
|
||||
confBytes, err := ioutil.ReadFile(confPath)
|
||||
conf, err := loadConfigFile(confPath)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read config file: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
return loadForFileFromBytes(confBytes, filePath, kmsEncryptionContext)
|
||||
return parseCreationRuleForFile(conf, filePath, kmsEncryptionContext)
|
||||
}
|
||||
|
||||
// LoadDestinationRuleForFile works the same as LoadForFile, but gets the "creation_rule" from the matching destination_rule's
|
||||
// "recreation_rule".
|
||||
func LoadDestinationRuleForFile(confPath string, filePath string, kmsEncryptionContext map[string]*string) (*Config, error) {
|
||||
conf, err := loadConfigFile(confPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return parseDestinationRuleForFile(conf, filePath, kmsEncryptionContext)
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ func TestFindConfigFileRecursive(t *testing.T) {
|
||||
return nil, &os.PathError{}
|
||||
}}
|
||||
filepath, err := FindConfigFile(".")
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, expectedPath, filepath)
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ func TestFindConfigFileCurrentDir(t *testing.T) {
|
||||
return nil, &os.PathError{}
|
||||
}}
|
||||
filepath, err := FindConfigFile(".")
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, expectedPath, filepath)
|
||||
}
|
||||
|
||||
@@ -60,7 +60,7 @@ creation_rules:
|
||||
kms: "1"
|
||||
pgp: "2"
|
||||
gcp_kms: "3"
|
||||
- filename_regex: "somefilename.yml"
|
||||
- path_regex: somefilename.yml
|
||||
kms: bilbo
|
||||
pgp: baggins
|
||||
gcp_kms: precious
|
||||
@@ -123,16 +123,41 @@ var sampleInvalidConfig = []byte(`
|
||||
creation_rules:
|
||||
`)
|
||||
|
||||
var sampleConfigWithDestinationRule = []byte(`
|
||||
creation_rules:
|
||||
- path_regex: foobar*
|
||||
kms: "1"
|
||||
pgp: "2"
|
||||
gcp_kms: "3"
|
||||
- path_regex: ""
|
||||
kms: foo
|
||||
pgp: bar
|
||||
gcp_kms: baz
|
||||
destination_rules:
|
||||
- path_regex: ""
|
||||
s3_bucket: "foobar"
|
||||
s3_prefix: "test/"
|
||||
recreation_rule:
|
||||
pgp: newpgp
|
||||
`)
|
||||
|
||||
func parseConfigFile(confBytes []byte, t *testing.T) *configFile {
|
||||
conf := &configFile{}
|
||||
err := conf.load(confBytes)
|
||||
assert.Nil(t, err)
|
||||
return conf
|
||||
}
|
||||
|
||||
func TestLoadConfigFile(t *testing.T) {
|
||||
expected := configFile{
|
||||
CreationRules: []creationRule{
|
||||
creationRule{
|
||||
{
|
||||
PathRegex: "foobar*",
|
||||
KMS: "1",
|
||||
PGP: "2",
|
||||
GCPKMS: "3",
|
||||
},
|
||||
creationRule{
|
||||
{
|
||||
PathRegex: "",
|
||||
KMS: "foo",
|
||||
PGP: "bar",
|
||||
@@ -143,7 +168,7 @@ func TestLoadConfigFile(t *testing.T) {
|
||||
|
||||
conf := configFile{}
|
||||
err := conf.load(sampleConfig)
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, expected, conf)
|
||||
}
|
||||
|
||||
@@ -178,44 +203,44 @@ func TestLoadConfigFileWithGroups(t *testing.T) {
|
||||
|
||||
conf := configFile{}
|
||||
err := conf.load(sampleConfigWithGroups)
|
||||
assert.Equal(t, nil, err)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, expected, conf)
|
||||
}
|
||||
|
||||
func TestLoadInvalidConfigFile(t *testing.T) {
|
||||
_, err := loadForFileFromBytes(sampleInvalidConfig, "foobar2000", nil)
|
||||
_, err := parseCreationRuleForFile(parseConfigFile(sampleInvalidConfig, t), "foobar2000", nil)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestKeyGroupsForFile(t *testing.T) {
|
||||
conf, err := loadForFileFromBytes(sampleConfig, "foobar2000", nil)
|
||||
assert.Equal(t, nil, err)
|
||||
conf, err := parseCreationRuleForFile(parseConfigFile(sampleConfig, t), "foobar2000", nil)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "2", conf.KeyGroups[0][0].ToString())
|
||||
assert.Equal(t, "1", conf.KeyGroups[0][1].ToString())
|
||||
conf, err = loadForFileFromBytes(sampleConfig, "whatever", nil)
|
||||
assert.Equal(t, nil, err)
|
||||
conf, err = parseCreationRuleForFile(parseConfigFile(sampleConfig, t), "whatever", nil)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "bar", conf.KeyGroups[0][0].ToString())
|
||||
assert.Equal(t, "foo", conf.KeyGroups[0][1].ToString())
|
||||
}
|
||||
|
||||
func TestKeyGroupsForFileWithPath(t *testing.T) {
|
||||
conf, err := loadForFileFromBytes(sampleConfigWithPath, "foo/bar2000", nil)
|
||||
assert.Equal(t, nil, err)
|
||||
conf, err := parseCreationRuleForFile(parseConfigFile(sampleConfigWithPath, t), "foo/bar2000", nil)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "2", conf.KeyGroups[0][0].ToString())
|
||||
assert.Equal(t, "1", conf.KeyGroups[0][1].ToString())
|
||||
conf, err = loadForFileFromBytes(sampleConfigWithPath, "somefilename.yml", nil)
|
||||
assert.Equal(t, nil, err)
|
||||
conf, err = parseCreationRuleForFile(parseConfigFile(sampleConfigWithPath, t), "somefilename.yml", nil)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "baggins", conf.KeyGroups[0][0].ToString())
|
||||
assert.Equal(t, "bilbo", conf.KeyGroups[0][1].ToString())
|
||||
conf, err = loadForFileFromBytes(sampleConfig, "whatever", nil)
|
||||
assert.Equal(t, nil, err)
|
||||
conf, err = parseCreationRuleForFile(parseConfigFile(sampleConfig, t), "whatever", nil)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "bar", conf.KeyGroups[0][0].ToString())
|
||||
assert.Equal(t, "foo", conf.KeyGroups[0][1].ToString())
|
||||
}
|
||||
|
||||
func TestKeyGroupsForFileWithGroups(t *testing.T) {
|
||||
conf, err := loadForFileFromBytes(sampleConfigWithGroups, "whatever", nil)
|
||||
assert.Equal(t, nil, err)
|
||||
conf, err := parseCreationRuleForFile(parseConfigFile(sampleConfigWithGroups, t), "whatever", nil)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "bar", conf.KeyGroups[0][0].ToString())
|
||||
assert.Equal(t, "foo", conf.KeyGroups[0][1].ToString())
|
||||
assert.Equal(t, "qux", conf.KeyGroups[1][0].ToString())
|
||||
@@ -223,18 +248,26 @@ func TestKeyGroupsForFileWithGroups(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestLoadConfigFileWithUnencryptedSuffix(t *testing.T) {
|
||||
conf, err := loadForFileFromBytes(sampleConfigWithSuffixParameters, "foobar", nil)
|
||||
assert.Equal(t, nil, err)
|
||||
conf, err := parseCreationRuleForFile(parseConfigFile(sampleConfigWithSuffixParameters, t), "foobar", nil)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "_unencrypted", conf.UnencryptedSuffix)
|
||||
}
|
||||
|
||||
func TestLoadConfigFileWithEncryptedSuffix(t *testing.T) {
|
||||
conf, err := loadForFileFromBytes(sampleConfigWithSuffixParameters, "barfoo", nil)
|
||||
assert.Equal(t, nil, err)
|
||||
conf, err := parseCreationRuleForFile(parseConfigFile(sampleConfigWithSuffixParameters, t), "barfoo", nil)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "_enc", conf.EncryptedSuffix)
|
||||
}
|
||||
|
||||
func TestLoadConfigFileWithInvalidParameters(t *testing.T) {
|
||||
_, err := loadForFileFromBytes(sampleConfigWithInvalidParameters, "foobar", nil)
|
||||
_, err := parseCreationRuleForFile(parseConfigFile(sampleConfigWithInvalidParameters, t), "foobar", nil)
|
||||
assert.NotNil(t, err)
|
||||
}
|
||||
|
||||
func TestLoadConfigFileWithDestinationRule(t *testing.T) {
|
||||
conf, err := parseDestinationRuleForFile(parseConfigFile(sampleConfigWithDestinationRule, t), "barfoo", nil)
|
||||
assert.Nil(t, err)
|
||||
assert.Equal(t, "newpgp", conf.KeyGroups[0][0].ToString())
|
||||
assert.NotNil(t, conf.Destination)
|
||||
assert.Equal(t, "s3://foobar/test/barfoo", conf.Destination.Path("barfoo"))
|
||||
}
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
creation_rules:
|
||||
- filename_regex: test_roundtrip_keygroups.yaml
|
||||
- path_regex: test_roundtrip_keygroups.yaml
|
||||
key_groups:
|
||||
- pgp:
|
||||
- 1022470DE3F0BC54BC6AB62DE05550BC07FB1A0A
|
||||
- pgp:
|
||||
- 729D26A79482B5A20DEAD0A76945978B930DD7A2
|
||||
- filename_regex: test_roundtrip_keygroups_missing_decryption_key.yaml
|
||||
- path_regex: test_roundtrip_keygroups_missing_decryption_key.yaml
|
||||
key_groups:
|
||||
- pgp:
|
||||
- 1022470DE3F0BC54BC6AB62DE05550BC07FB1A0A
|
||||
- pgp:
|
||||
- 620B9A4C96230B91E7473D20113D2B26EA0890C7
|
||||
- pgp: 1022470DE3F0BC54BC6AB62DE05550BC07FB1A0A
|
||||
destination_rules:
|
||||
- s3_bucket: "sops-publish-functional-tests"
|
||||
s3_prefix: "functional-test/"
|
||||
path_regex: test_encrypt_publish_s3.json
|
||||
reencryption_rule:
|
||||
pgp: 620B9A4C96230B91E7473D20113D2B26EA0890C7
|
||||
|
||||
@@ -74,6 +74,37 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn publish_json_file_s3() {
|
||||
let file_path = prepare_temp_file("test_encrypt_publish_s3.json",
|
||||
b"{
|
||||
\"foo\": 2,
|
||||
\"bar\": \"baz\"
|
||||
}");
|
||||
assert!(Command::new(SOPS_BINARY_PATH)
|
||||
.arg("-e")
|
||||
.arg("-i")
|
||||
.arg(file_path.clone())
|
||||
.output()
|
||||
.expect("Error running sops")
|
||||
.status
|
||||
.success(),
|
||||
"SOPS failed to encrypt a file");
|
||||
assert!(Command::new(SOPS_BINARY_PATH)
|
||||
.arg("publish")
|
||||
.arg("--yes")
|
||||
.arg(file_path.clone())
|
||||
.output()
|
||||
.expect("Error running sops")
|
||||
.status
|
||||
.success(),
|
||||
"sops failed to publish a file to S3");
|
||||
|
||||
//TODO: Check that file exists in S3 Bucket
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn encrypt_json_file_kms() {
|
||||
|
||||
36
publish/gcs.go
Normal file
36
publish/gcs.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package publish
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"cloud.google.com/go/storage"
|
||||
)
|
||||
|
||||
type GCSDestination struct {
|
||||
gcsBucket string
|
||||
gcsPrefix string
|
||||
}
|
||||
|
||||
func NewGCSDestination(gcsBucket string, gcsPrefix string) *GCSDestination {
|
||||
return &GCSDestination{gcsBucket, gcsPrefix}
|
||||
}
|
||||
|
||||
func (gcsd *GCSDestination) Path(fileName string) string {
|
||||
return fmt.Sprintf("gcs://%s/%s%s", gcsd.gcsBucket, gcsd.gcsPrefix, fileName)
|
||||
}
|
||||
|
||||
func (gcsd *GCSDestination) Upload(fileContents []byte, fileName string) error {
|
||||
ctx := context.Background()
|
||||
client, err := storage.NewClient(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
wc := client.Bucket(gcsd.gcsBucket).Object(gcsd.gcsPrefix + fileName).NewWriter(ctx)
|
||||
defer wc.Close()
|
||||
_, err = wc.Write(fileContents)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
6
publish/publish.go
Normal file
6
publish/publish.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package publish
|
||||
|
||||
type Destination interface {
|
||||
Upload(fileContents []byte, fileName string) error
|
||||
Path(fileName string) string
|
||||
}
|
||||
38
publish/s3.go
Normal file
38
publish/s3.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package publish
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/s3"
|
||||
)
|
||||
|
||||
type S3Destination struct {
|
||||
s3Bucket string
|
||||
s3Prefix string
|
||||
}
|
||||
|
||||
func NewS3Destination(s3Bucket string, s3Prefix string) *S3Destination {
|
||||
return &S3Destination{s3Bucket, s3Prefix}
|
||||
}
|
||||
|
||||
func (s3d *S3Destination) Path(fileName string) string {
|
||||
return fmt.Sprintf("s3://%s/%s%s", s3d.s3Bucket, s3d.s3Prefix, fileName)
|
||||
}
|
||||
|
||||
func (s3d *S3Destination) Upload(fileContents []byte, fileName string) error {
|
||||
sess := session.Must(session.NewSession())
|
||||
svc := s3.New(sess)
|
||||
input := &s3.PutObjectInput{
|
||||
Body: aws.ReadSeekCloser(bytes.NewReader(fileContents)),
|
||||
Bucket: aws.String(s3d.s3Bucket),
|
||||
Key: aws.String(s3d.s3Prefix + fileName),
|
||||
}
|
||||
_, err := svc.PutObject(input)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
43
vendor/github.com/googleapis/gax-go/CODE_OF_CONDUCT.md
generated
vendored
Normal file
43
vendor/github.com/googleapis/gax-go/CODE_OF_CONDUCT.md
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
# Contributor Code of Conduct
|
||||
|
||||
As contributors and maintainers of this project,
|
||||
and in the interest of fostering an open and welcoming community,
|
||||
we pledge to respect all people who contribute through reporting issues,
|
||||
posting feature requests, updating documentation,
|
||||
submitting pull requests or patches, and other activities.
|
||||
|
||||
We are committed to making participation in this project
|
||||
a harassment-free experience for everyone,
|
||||
regardless of level of experience, gender, gender identity and expression,
|
||||
sexual orientation, disability, personal appearance,
|
||||
body size, race, ethnicity, age, religion, or nationality.
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
|
||||
* The use of sexualized language or imagery
|
||||
* Personal attacks
|
||||
* Trolling or insulting/derogatory comments
|
||||
* Public or private harassment
|
||||
* Publishing other's private information,
|
||||
such as physical or electronic
|
||||
addresses, without explicit permission
|
||||
* Other unethical or unprofessional conduct.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct.
|
||||
By adopting this Code of Conduct,
|
||||
project maintainers commit themselves to fairly and consistently
|
||||
applying these principles to every aspect of managing this project.
|
||||
Project maintainers who do not follow or enforce the Code of Conduct
|
||||
may be permanently removed from the project team.
|
||||
|
||||
This code of conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community.
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior
|
||||
may be reported by opening an issue
|
||||
or contacting one or more of the project maintainers.
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.2.0,
|
||||
available at [http://contributor-covenant.org/version/1/2/0/](http://contributor-covenant.org/version/1/2/0/)
|
||||
32
vendor/github.com/googleapis/gax-go/CONTRIBUTING.md
generated
vendored
Normal file
32
vendor/github.com/googleapis/gax-go/CONTRIBUTING.md
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
Want to contribute? Great! First, read this page (including the small print at the end).
|
||||
|
||||
### Before you contribute
|
||||
Before we can use your code, you must sign the
|
||||
[Google Individual Contributor License Agreement]
|
||||
(https://cla.developers.google.com/about/google-individual)
|
||||
(CLA), which you can do online. The CLA is necessary mainly because you own the
|
||||
copyright to your changes, even after your contribution becomes part of our
|
||||
codebase, so we need your permission to use and distribute your code. We also
|
||||
need to be sure of various other things—for instance that you'll tell us if you
|
||||
know that your code infringes on other people's patents. You don't have to sign
|
||||
the CLA until after you've submitted your code for review and a member has
|
||||
approved it, but you must do it before we can put your code into our codebase.
|
||||
Before you start working on a larger contribution, you should get in touch with
|
||||
us first through the issue tracker with your idea so that we can help out and
|
||||
possibly guide you. Coordinating up front makes it much easier to avoid
|
||||
frustration later on.
|
||||
|
||||
### Code reviews
|
||||
All submissions, including submissions by project members, require review. We
|
||||
use Github pull requests for this purpose.
|
||||
|
||||
### Breaking code changes
|
||||
When a breaking change is added, CI/CD will fail. If the change is expected,
|
||||
add a BREAKING_CHANGE_ACCEPTABLE=<reason> line to the CL description. This will
|
||||
cause CI/CD to skip checking breaking changes.
|
||||
|
||||
### The small print
|
||||
Contributions made by corporations are covered by a different agreement than
|
||||
the one above, the
|
||||
[Software Grant and Corporate Contributor License Agreement]
|
||||
(https://cla.developers.google.com/about/google-corporate).
|
||||
27
vendor/github.com/googleapis/gax-go/LICENSE
generated
vendored
Normal file
27
vendor/github.com/googleapis/gax-go/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
Copyright 2016, Google Inc.
|
||||
All rights reserved.
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google Inc. nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
27
vendor/github.com/googleapis/gax-go/README.md
generated
vendored
Normal file
27
vendor/github.com/googleapis/gax-go/README.md
generated
vendored
Normal file
@@ -0,0 +1,27 @@
|
||||
Google API Extensions for Go
|
||||
============================
|
||||
|
||||
[](https://godoc.org/github.com/googleapis/gax-go)
|
||||
|
||||
Google API Extensions for Go (gax-go) is a set of modules which aids the
|
||||
development of APIs for clients and servers based on `gRPC` and Google API
|
||||
conventions.
|
||||
|
||||
To install the API extensions, use:
|
||||
|
||||
```
|
||||
go get -u github.com/googleapis/gax-go
|
||||
```
|
||||
|
||||
**Note:** Application code will rarely need to use this library directly,
|
||||
but the code generated automatically from API definition files can use it
|
||||
to simplify code generation and to provide more convenient and idiomatic API surface.
|
||||
|
||||
Go Versions
|
||||
===========
|
||||
This library requires Go 1.6 or above.
|
||||
|
||||
License
|
||||
=======
|
||||
BSD - please see [LICENSE](https://github.com/googleapis/gax-go/blob/master/LICENSE)
|
||||
for more information.
|
||||
30
vendor/github.com/googleapis/gax-go/RELEASING.md
generated
vendored
Normal file
30
vendor/github.com/googleapis/gax-go/RELEASING.md
generated
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
# How to release v1
|
||||
|
||||
1. Determine the current release version with `git tag -l`. It should look
|
||||
something like `vX.Y.Z`. We'll call the current version `$CV` and the new
|
||||
version `$NV`.
|
||||
1. On master, run `git log $CV..` to list all the changes since the last
|
||||
release.
|
||||
a. NOTE: Some commits may pertain to only v1 or v2. Manually introspect
|
||||
each commit to figure which occurred in v1.
|
||||
1. Edit `CHANGES.md` to include a summary of the changes.
|
||||
1. Mail the CL containing the `CHANGES.md` changes. When the CL is approved,
|
||||
submit it.
|
||||
1. Without submitting any other CLs:
|
||||
a. Switch to master.
|
||||
b. `git pull`
|
||||
c. Tag the repo with the next version: `git tag $NV`. It should be of the
|
||||
form `v1.Y.Z`.
|
||||
d. Push the tag: `git push origin $NV`.
|
||||
1. Update [the releases page](https://github.com/googleapis/google-cloud-go/releases)
|
||||
with the new release, copying the contents of the CHANGES.md.
|
||||
|
||||
# How to release v2
|
||||
|
||||
Same process as v1, once again noting that the commit list may include v1
|
||||
commits (which should be pruned out). Note also whilst v1 tags are `v1.Y.Z`, v2
|
||||
tags are `v2.Y.Z`.
|
||||
|
||||
# On releasing multiple major versions
|
||||
|
||||
Please see https://github.com/golang/go/wiki/Modules#releasing-modules-v2-or-higher.
|
||||
71
vendor/github.com/googleapis/gax-go/call_option.go
generated
vendored
Normal file
71
vendor/github.com/googleapis/gax-go/call_option.go
generated
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright 2016, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package gax
|
||||
|
||||
import (
|
||||
v2 "github.com/googleapis/gax-go/v2"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
)
|
||||
|
||||
// CallOption is an option used by Invoke to control behaviors of RPC calls.
|
||||
// CallOption works by modifying relevant fields of CallSettings.
|
||||
type CallOption = v2.CallOption
|
||||
|
||||
// Retryer is used by Invoke to determine retry behavior.
|
||||
type Retryer = v2.Retryer
|
||||
|
||||
// WithRetry sets CallSettings.Retry to fn.
|
||||
func WithRetry(fn func() Retryer) CallOption {
|
||||
return v2.WithRetry(fn)
|
||||
}
|
||||
|
||||
// OnCodes returns a Retryer that retries if and only if
|
||||
// the previous attempt returns a GRPC error whose error code is stored in cc.
|
||||
// Pause times between retries are specified by bo.
|
||||
//
|
||||
// bo is only used for its parameters; each Retryer has its own copy.
|
||||
func OnCodes(cc []codes.Code, bo Backoff) Retryer {
|
||||
return v2.OnCodes(cc, bo)
|
||||
}
|
||||
|
||||
// Backoff implements exponential backoff.
|
||||
// The wait time between retries is a random value between 0 and the "retry envelope".
|
||||
// The envelope starts at Initial and increases by the factor of Multiplier every retry,
|
||||
// but is capped at Max.
|
||||
type Backoff = v2.Backoff
|
||||
|
||||
// WithGRPCOptions allows passing gRPC call options during client creation.
|
||||
func WithGRPCOptions(opt ...grpc.CallOption) CallOption {
|
||||
return v2.WithGRPCOptions(opt...)
|
||||
}
|
||||
|
||||
// CallSettings allow fine-grained control over how calls are made.
|
||||
type CallSettings = v2.CallSettings
|
||||
39
vendor/github.com/googleapis/gax-go/gax.go
generated
vendored
Normal file
39
vendor/github.com/googleapis/gax-go/gax.go
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
// Copyright 2016, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// Package gax contains a set of modules which aid the development of APIs
|
||||
// for clients and servers based on gRPC and Google API conventions.
|
||||
//
|
||||
// Application code will rarely need to use this library directly.
|
||||
// However, code generated automatically from API definition files can use it
|
||||
// to simplify code generation and to provide more convenient and idiomatic API surfaces.
|
||||
package gax
|
||||
|
||||
// Version specifies the gax version.
|
||||
const Version = "1.0.1"
|
||||
11
vendor/github.com/googleapis/gax-go/go.mod
generated
vendored
Normal file
11
vendor/github.com/googleapis/gax-go/go.mod
generated
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
module github.com/googleapis/gax-go
|
||||
|
||||
require (
|
||||
github.com/golang/protobuf v1.3.1
|
||||
github.com/googleapis/gax-go/v2 v2.0.2
|
||||
golang.org/x/exp v0.0.0-20190221220918-438050ddec5e
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b
|
||||
google.golang.org/grpc v1.19.0
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099
|
||||
)
|
||||
44
vendor/github.com/googleapis/gax-go/go.sum
generated
vendored
Normal file
44
vendor/github.com/googleapis/gax-go/go.sum
generated
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/googleapis/gax-go/v2 v2.0.2 h1:/rNgUniLy2vDXiK2xyJOcirGpC3G99dtK1NWx26WZ8Y=
|
||||
github.com/googleapis/gax-go/v2 v2.0.2/go.mod h1:LLvjysVCY1JZeum8Z6l8qUty8fiNwE08qbEPm1M08qg=
|
||||
github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
golang.org/x/exp v0.0.0-20190221220918-438050ddec5e h1:dVreTP5bOOWt5GFwwvgTE2iU0TkIqi2x3r0b8qGlp6k=
|
||||
golang.org/x/exp v0.0.0-20190221220918-438050ddec5e/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7 h1:00BeQWmeaGazuOrq8Q5K5d3/cHaGuFrZzpaHBXfrsUA=
|
||||
golang.org/x/lint v0.0.0-20180702182130-06c8688daad7/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3 h1:x/bBzNauLQAlE3fLku/xy92Y8QwKX5HZymrMz2IiKFc=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d h1:g9qWBGx4puODJTMVyoPrpoxPFgVGd+z1DZwjfRu4d0I=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522 h1:Ve1ORMCxvRmSXBwJK+t3Oy+V2vRW2OetUQBq4rJIkZE=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52 h1:JG/0uqcGdTNgq7FdU+61l5Pdmb8putNZlXb65bJBROs=
|
||||
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b h1:qMK98NmNCRVDIYFycQ5yVRkvgDUFfdP8Ip4KqmDEB7g=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/grpc v1.16.0 h1:dz5IJGuC2BB7qXR5AyHNwAUBhZscK2xVez7mznh72sY=
|
||||
google.golang.org/grpc v1.16.0/go.mod h1:0JHn/cJsOMiMfNA9+DeHDlAU7KAAB5GDlYFpa9MZMio=
|
||||
google.golang.org/grpc v1.19.0 h1:cfg4PD8YEdSFnm7qLV4++93WcmhH2nIUhMjhdCvl3j8=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858 h1:wN+eVZ7U+gqdqkec6C6VXR1OFf9a5Ul9ETzeYsYv20g=
|
||||
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099 h1:XJP7lxbSxWLOMNdBE4B/STaqVy6L73o0knwj2vIlxnw=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
40
vendor/github.com/googleapis/gax-go/header.go
generated
vendored
Normal file
40
vendor/github.com/googleapis/gax-go/header.go
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
// Copyright 2018, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package gax
|
||||
|
||||
import v2 "github.com/googleapis/gax-go/v2"
|
||||
|
||||
// XGoogHeader is for use by the Google Cloud Libraries only.
|
||||
//
|
||||
// XGoogHeader formats key-value pairs.
|
||||
// The resulting string is suitable for x-goog-api-client header.
|
||||
func XGoogHeader(keyval ...string) string {
|
||||
return v2.XGoogHeader(keyval...)
|
||||
}
|
||||
43
vendor/github.com/googleapis/gax-go/internal/kokoro/check_incompat_changes.sh
generated
vendored
Executable file
43
vendor/github.com/googleapis/gax-go/internal/kokoro/check_incompat_changes.sh
generated
vendored
Executable file
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Display commands being run
|
||||
set -x
|
||||
|
||||
# Only run apidiff checks on go1.12 (we only need it once).
|
||||
# TODO(deklerk) We should pass an environment variable from kokoro to decide
|
||||
# this logic instead.
|
||||
if [[ `go version` != *"go1.12"* ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if git log -1 | grep BREAKING_CHANGE_ACCEPTABLE; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
export GO111MODULE=on
|
||||
|
||||
go install golang.org/x/exp/cmd/apidiff
|
||||
|
||||
# We compare against master@HEAD. This is unfortunate in some cases: if you're
|
||||
# working on an out-of-date branch, and master gets some new feature (that has
|
||||
# nothing to do with your work on your branch), you'll get an error message.
|
||||
# Thankfully the fix is quite simple: rebase your branch.
|
||||
git clone https://github.com/googleapis/gax-go /tmp/gax
|
||||
|
||||
for dir in "" "/v2"; do
|
||||
pkg="github.com/googleapis/gax-go$dir"
|
||||
echo "Testing $pkg"
|
||||
|
||||
# cd to the exact directory that specifies the go module so that it doesn't
|
||||
# use the module cache. https://go-review.googlesource.com/c/exp/+/155058
|
||||
cd "/tmp/gax$dir"
|
||||
apidiff -w /tmp/pkg.master $pkg
|
||||
cd - > /dev/null
|
||||
|
||||
# TODO(deklerk) there's probably a nicer way to do this that doesn't require
|
||||
# two invocations
|
||||
if ! apidiff -incompatible /tmp/pkg.master $pkg | (! read); then
|
||||
apidiff -incompatible /tmp/pkg.master $pkg
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
47
vendor/github.com/googleapis/gax-go/internal/kokoro/test.sh
generated
vendored
Executable file
47
vendor/github.com/googleapis/gax-go/internal/kokoro/test.sh
generated
vendored
Executable file
@@ -0,0 +1,47 @@
|
||||
#!/bin/bash
|
||||
|
||||
# TODO(deklerk) Add integration tests when it's secure to do so. b/64723143
|
||||
|
||||
# Fail on any error
|
||||
set -eo pipefail
|
||||
|
||||
# Display commands being run
|
||||
set -x
|
||||
|
||||
# cd to project dir on Kokoro instance
|
||||
cd github/gax-go
|
||||
|
||||
go version
|
||||
|
||||
# Set $GOPATH
|
||||
export GOPATH="$HOME/go"
|
||||
export GAX_HOME=$GOPATH/src/github.com/googleapis/gax-go
|
||||
export PATH="$GOPATH/bin:$PATH"
|
||||
mkdir -p $GAX_HOME
|
||||
|
||||
# Move code into $GOPATH and get dependencies
|
||||
git clone . $GAX_HOME
|
||||
cd $GAX_HOME
|
||||
|
||||
try3() { eval "$*" || eval "$*" || eval "$*"; }
|
||||
|
||||
download_deps() {
|
||||
if [[ `go version` == *"go1.11"* ]] || [[ `go version` == *"go1.12"* ]]; then
|
||||
export GO111MODULE=on
|
||||
# All packages, including +build tools, are fetched.
|
||||
try3 go mod download
|
||||
else
|
||||
# Because we don't provide -tags tools, the +build tools
|
||||
# dependencies aren't fetched.
|
||||
try3 go get -v -t ./...
|
||||
fi
|
||||
}
|
||||
|
||||
download_deps
|
||||
./internal/kokoro/check_incompat_changes.sh
|
||||
./internal/kokoro/vet.sh
|
||||
go test -race -v . 2>&1 | tee $KOKORO_ARTIFACTS_DIR/$KOKORO_GERRIT_CHANGE_NUMBER.txt
|
||||
|
||||
cd v2
|
||||
download_deps
|
||||
go test -race -v . 2>&1 | tee $KOKORO_ARTIFACTS_DIR/$KOKORO_GERRIT_CHANGE_NUMBER.txt
|
||||
24
vendor/github.com/googleapis/gax-go/internal/kokoro/trampoline.sh
generated
vendored
Normal file
24
vendor/github.com/googleapis/gax-go/internal/kokoro/trampoline.sh
generated
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
# Copyright 2018 Google 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.
|
||||
set -eo pipefail
|
||||
# Always run the cleanup script, regardless of the success of bouncing into
|
||||
# the container.
|
||||
function cleanup() {
|
||||
chmod +x ${KOKORO_GFILE_DIR}/trampoline_cleanup.sh
|
||||
${KOKORO_GFILE_DIR}/trampoline_cleanup.sh
|
||||
echo "cleanup";
|
||||
}
|
||||
trap cleanup EXIT
|
||||
python3 "${KOKORO_GFILE_DIR}/trampoline_v1.py"
|
||||
39
vendor/github.com/googleapis/gax-go/internal/kokoro/vet.sh
generated
vendored
Executable file
39
vendor/github.com/googleapis/gax-go/internal/kokoro/vet.sh
generated
vendored
Executable file
@@ -0,0 +1,39 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Fail on any error
|
||||
set -eo pipefail
|
||||
|
||||
# Display commands being run
|
||||
set -x
|
||||
|
||||
# Only run the linter on go1.11, since it needs type aliases (and we only care about its output once).
|
||||
# TODO(deklerk) We should pass an environment variable from kokoro to decide this logic instead.
|
||||
if [[ `go version` != *"go1.12"* ]]; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
export GO111MODULE=on
|
||||
|
||||
go install \
|
||||
github.com/golang/protobuf/proto \
|
||||
github.com/golang/protobuf/protoc-gen-go \
|
||||
golang.org/x/lint/golint \
|
||||
golang.org/x/tools/cmd/goimports \
|
||||
honnef.co/go/tools/cmd/staticcheck
|
||||
|
||||
# Fail if a dependency was added without the necessary go.mod/go.sum change
|
||||
# being part of the commit.
|
||||
go mod tidy
|
||||
git diff go.mod | tee /dev/stderr | (! read)
|
||||
git diff go.sum | tee /dev/stderr | (! read)
|
||||
|
||||
# Easier to debug CI.
|
||||
pwd
|
||||
|
||||
# Look at all .go files (ignoring .pb.go files) and make sure they have a Copyright. Fail if any don't.
|
||||
git ls-files "*[^.pb].go" | xargs grep -L "\(Copyright [0-9]\{4,\}\)" 2>&1 | tee /dev/stderr | (! read)
|
||||
gofmt -s -d -l . 2>&1 | tee /dev/stderr | (! read)
|
||||
goimports -l . 2>&1 | tee /dev/stderr | (! read)
|
||||
|
||||
golint ./... 2>&1 | tee /dev/stderr | (! read)
|
||||
staticcheck ./...
|
||||
52
vendor/github.com/googleapis/gax-go/invoke.go
generated
vendored
Normal file
52
vendor/github.com/googleapis/gax-go/invoke.go
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
// Copyright 2016, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package gax
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
v2 "github.com/googleapis/gax-go/v2"
|
||||
)
|
||||
|
||||
// APICall is a user defined call stub.
|
||||
type APICall = v2.APICall
|
||||
|
||||
// Invoke calls the given APICall,
|
||||
// performing retries as specified by opts, if any.
|
||||
func Invoke(ctx context.Context, call APICall, opts ...CallOption) error {
|
||||
return v2.Invoke(ctx, call, opts...)
|
||||
}
|
||||
|
||||
// Sleep is similar to time.Sleep, but it can be interrupted by ctx.Done() closing.
|
||||
// If interrupted, Sleep returns ctx.Err().
|
||||
func Sleep(ctx context.Context, d time.Duration) error {
|
||||
return v2.Sleep(ctx, d)
|
||||
}
|
||||
33
vendor/github.com/googleapis/gax-go/tools.go
generated
vendored
Normal file
33
vendor/github.com/googleapis/gax-go/tools.go
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
// +build tools
|
||||
|
||||
// Copyright 2019 Google LLC
|
||||
//
|
||||
// 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.
|
||||
|
||||
// This package exists to cause `go mod` and `go get` to believe these tools
|
||||
// are dependencies, even though they are not runtime dependencies of any
|
||||
// package (these are tools used by our CI builds). This means they will appear
|
||||
// in our `go.mod` file, but will not be a part of the build. Also, since the
|
||||
// build target is something non-existent, these should not be included in any
|
||||
// binaries.
|
||||
|
||||
package gax
|
||||
|
||||
import (
|
||||
_ "github.com/golang/protobuf/proto"
|
||||
_ "github.com/golang/protobuf/protoc-gen-go"
|
||||
_ "golang.org/x/exp/cmd/apidiff"
|
||||
_ "golang.org/x/lint/golint"
|
||||
_ "golang.org/x/tools/cmd/goimports"
|
||||
_ "honnef.co/go/tools/cmd/staticcheck"
|
||||
)
|
||||
161
vendor/github.com/googleapis/gax-go/v2/call_option.go
generated
vendored
Normal file
161
vendor/github.com/googleapis/gax-go/v2/call_option.go
generated
vendored
Normal file
@@ -0,0 +1,161 @@
|
||||
// Copyright 2016, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package gax
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// CallOption is an option used by Invoke to control behaviors of RPC calls.
|
||||
// CallOption works by modifying relevant fields of CallSettings.
|
||||
type CallOption interface {
|
||||
// Resolve applies the option by modifying cs.
|
||||
Resolve(cs *CallSettings)
|
||||
}
|
||||
|
||||
// Retryer is used by Invoke to determine retry behavior.
|
||||
type Retryer interface {
|
||||
// Retry reports whether a request should be retriedand how long to pause before retrying
|
||||
// if the previous attempt returned with err. Invoke never calls Retry with nil error.
|
||||
Retry(err error) (pause time.Duration, shouldRetry bool)
|
||||
}
|
||||
|
||||
type retryerOption func() Retryer
|
||||
|
||||
func (o retryerOption) Resolve(s *CallSettings) {
|
||||
s.Retry = o
|
||||
}
|
||||
|
||||
// WithRetry sets CallSettings.Retry to fn.
|
||||
func WithRetry(fn func() Retryer) CallOption {
|
||||
return retryerOption(fn)
|
||||
}
|
||||
|
||||
// OnCodes returns a Retryer that retries if and only if
|
||||
// the previous attempt returns a GRPC error whose error code is stored in cc.
|
||||
// Pause times between retries are specified by bo.
|
||||
//
|
||||
// bo is only used for its parameters; each Retryer has its own copy.
|
||||
func OnCodes(cc []codes.Code, bo Backoff) Retryer {
|
||||
return &boRetryer{
|
||||
backoff: bo,
|
||||
codes: append([]codes.Code(nil), cc...),
|
||||
}
|
||||
}
|
||||
|
||||
type boRetryer struct {
|
||||
backoff Backoff
|
||||
codes []codes.Code
|
||||
}
|
||||
|
||||
func (r *boRetryer) Retry(err error) (time.Duration, bool) {
|
||||
st, ok := status.FromError(err)
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
c := st.Code()
|
||||
for _, rc := range r.codes {
|
||||
if c == rc {
|
||||
return r.backoff.Pause(), true
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// Backoff implements exponential backoff.
|
||||
// The wait time between retries is a random value between 0 and the "retry envelope".
|
||||
// The envelope starts at Initial and increases by the factor of Multiplier every retry,
|
||||
// but is capped at Max.
|
||||
type Backoff struct {
|
||||
// Initial is the initial value of the retry envelope, defaults to 1 second.
|
||||
Initial time.Duration
|
||||
|
||||
// Max is the maximum value of the retry envelope, defaults to 30 seconds.
|
||||
Max time.Duration
|
||||
|
||||
// Multiplier is the factor by which the retry envelope increases.
|
||||
// It should be greater than 1 and defaults to 2.
|
||||
Multiplier float64
|
||||
|
||||
// cur is the current retry envelope
|
||||
cur time.Duration
|
||||
}
|
||||
|
||||
// Pause returns the next time.Duration that the caller should use to backoff.
|
||||
func (bo *Backoff) Pause() time.Duration {
|
||||
if bo.Initial == 0 {
|
||||
bo.Initial = time.Second
|
||||
}
|
||||
if bo.cur == 0 {
|
||||
bo.cur = bo.Initial
|
||||
}
|
||||
if bo.Max == 0 {
|
||||
bo.Max = 30 * time.Second
|
||||
}
|
||||
if bo.Multiplier < 1 {
|
||||
bo.Multiplier = 2
|
||||
}
|
||||
// Select a duration between 1ns and the current max. It might seem
|
||||
// counterintuitive to have so much jitter, but
|
||||
// https://www.awsarchitectureblog.com/2015/03/backoff.html argues that
|
||||
// that is the best strategy.
|
||||
d := time.Duration(1 + rand.Int63n(int64(bo.cur)))
|
||||
bo.cur = time.Duration(float64(bo.cur) * bo.Multiplier)
|
||||
if bo.cur > bo.Max {
|
||||
bo.cur = bo.Max
|
||||
}
|
||||
return d
|
||||
}
|
||||
|
||||
type grpcOpt []grpc.CallOption
|
||||
|
||||
func (o grpcOpt) Resolve(s *CallSettings) {
|
||||
s.GRPC = o
|
||||
}
|
||||
|
||||
// WithGRPCOptions allows passing gRPC call options during client creation.
|
||||
func WithGRPCOptions(opt ...grpc.CallOption) CallOption {
|
||||
return grpcOpt(append([]grpc.CallOption(nil), opt...))
|
||||
}
|
||||
|
||||
// CallSettings allow fine-grained control over how calls are made.
|
||||
type CallSettings struct {
|
||||
// Retry returns a Retryer to be used to control retry logic of a method call.
|
||||
// If Retry is nil or the returned Retryer is nil, the call will not be retried.
|
||||
Retry func() Retryer
|
||||
|
||||
// CallOptions to be forwarded to GRPC.
|
||||
GRPC []grpc.CallOption
|
||||
}
|
||||
88
vendor/github.com/googleapis/gax-go/v2/call_option_test.go
generated
vendored
Normal file
88
vendor/github.com/googleapis/gax-go/v2/call_option_test.go
generated
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
// Copyright 2016, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package gax
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
var _ Retryer = &boRetryer{}
|
||||
|
||||
func TestBackofDefault(t *testing.T) {
|
||||
backoff := Backoff{}
|
||||
|
||||
max := []time.Duration{1, 2, 4, 8, 16, 30, 30, 30, 30, 30}
|
||||
for i, m := range max {
|
||||
max[i] = m * time.Second
|
||||
}
|
||||
|
||||
for i, w := range max {
|
||||
if d := backoff.Pause(); d > w {
|
||||
t.Errorf("Backoff duration should be at most %s, got %s", w, d)
|
||||
} else if i < len(max)-1 && backoff.cur != max[i+1] {
|
||||
t.Errorf("current envelope is %s, want %s", backoff.cur, max[i+1])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBackoffExponential(t *testing.T) {
|
||||
backoff := Backoff{Initial: 1, Max: 20, Multiplier: 2}
|
||||
want := []time.Duration{1, 2, 4, 8, 16, 20, 20, 20, 20, 20}
|
||||
for _, w := range want {
|
||||
if d := backoff.Pause(); d > w {
|
||||
t.Errorf("Backoff duration should be at most %s, got %s", w, d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestOnCodes(t *testing.T) {
|
||||
// Lint errors grpc.Errorf in 1.6. It mistakenly expects the first arg to Errorf to be a string.
|
||||
errf := status.Errorf
|
||||
apiErr := errf(codes.Unavailable, "")
|
||||
tests := []struct {
|
||||
c []codes.Code
|
||||
retry bool
|
||||
}{
|
||||
{nil, false},
|
||||
{[]codes.Code{codes.DeadlineExceeded}, false},
|
||||
{[]codes.Code{codes.DeadlineExceeded, codes.Unavailable}, true},
|
||||
{[]codes.Code{codes.Unavailable}, true},
|
||||
}
|
||||
for _, tst := range tests {
|
||||
b := OnCodes(tst.c, Backoff{})
|
||||
if _, retry := b.Retry(apiErr); retry != tst.retry {
|
||||
t.Errorf("retriable codes: %v, error: %s, retry: %t, want %t", tst.c, apiErr, retry, tst.retry)
|
||||
}
|
||||
}
|
||||
}
|
||||
94
vendor/github.com/googleapis/gax-go/v2/example_test.go
generated
vendored
Normal file
94
vendor/github.com/googleapis/gax-go/v2/example_test.go
generated
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
// Copyright 2019, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package gax_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/googleapis/gax-go/v2"
|
||||
"google.golang.org/grpc/codes"
|
||||
)
|
||||
|
||||
const someRPCTimeout = 5 * time.Minute
|
||||
|
||||
// Some result that the client might return.
|
||||
type fakeResponse struct{}
|
||||
|
||||
// Some client that can perform RPCs.
|
||||
type fakeClient struct{}
|
||||
|
||||
// PerformSomeRPC is a fake RPC that a client might perform.
|
||||
func (c *fakeClient) PerformSomeRPC(ctx context.Context) (*fakeResponse, error) {
|
||||
// An actual client would return something meaningful here.
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func ExampleOnCodes() {
|
||||
ctx := context.Background()
|
||||
c := &fakeClient{}
|
||||
|
||||
// UNKNOWN and UNAVAILABLE are typically safe to retry for idempotent RPCs.
|
||||
retryer := gax.OnCodes([]codes.Code{codes.Unknown, codes.Unavailable}, gax.Backoff{
|
||||
Initial: time.Second,
|
||||
Max: 32 * time.Second,
|
||||
Multiplier: 2,
|
||||
})
|
||||
|
||||
performSomeRPCWithRetry := func(ctx context.Context) (*fakeResponse, error) {
|
||||
for {
|
||||
resp, err := c.PerformSomeRPC(ctx)
|
||||
if err != nil {
|
||||
if delay, shouldRetry := retryer.Retry(err); shouldRetry {
|
||||
if err := gax.Sleep(ctx, delay); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
}
|
||||
|
||||
// It's recommended to set deadlines on RPCs and around retrying. This is
|
||||
// also usually preferred over setting some fixed number of retries: one
|
||||
// advantage this has is that backoff settings can be changed independently
|
||||
// of the deadline, whereas with a fixed number of retries the deadline
|
||||
// would be a constantly-shifting goalpost.
|
||||
ctxWithTimeout, cancel := context.WithDeadline(ctx, time.Now().Add(someRPCTimeout))
|
||||
defer cancel()
|
||||
|
||||
resp, err := performSomeRPCWithRetry(ctxWithTimeout)
|
||||
if err != nil {
|
||||
// TODO: handle err
|
||||
}
|
||||
_ = resp // TODO: use resp if err is nil
|
||||
}
|
||||
39
vendor/github.com/googleapis/gax-go/v2/gax.go
generated
vendored
Normal file
39
vendor/github.com/googleapis/gax-go/v2/gax.go
generated
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
// Copyright 2016, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
// Package gax contains a set of modules which aid the development of APIs
|
||||
// for clients and servers based on gRPC and Google API conventions.
|
||||
//
|
||||
// Application code will rarely need to use this library directly.
|
||||
// However, code generated automatically from API definition files can use it
|
||||
// to simplify code generation and to provide more convenient and idiomatic API surfaces.
|
||||
package gax
|
||||
|
||||
// Version specifies the gax-go version being used.
|
||||
const Version = "2.0.4"
|
||||
3
vendor/github.com/googleapis/gax-go/v2/go.mod
generated
vendored
Normal file
3
vendor/github.com/googleapis/gax-go/v2/go.mod
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
module github.com/googleapis/gax-go/v2
|
||||
|
||||
require google.golang.org/grpc v1.19.0
|
||||
25
vendor/github.com/googleapis/gax-go/v2/go.sum
generated
vendored
Normal file
25
vendor/github.com/googleapis/gax-go/v2/go.sum
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
|
||||
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d h1:g9qWBGx4puODJTMVyoPrpoxPFgVGd+z1DZwjfRu4d0I=
|
||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
|
||||
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522 h1:Ve1ORMCxvRmSXBwJK+t3Oy+V2vRW2OetUQBq4rJIkZE=
|
||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc=
|
||||
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/grpc v1.19.0 h1:cfg4PD8YEdSFnm7qLV4++93WcmhH2nIUhMjhdCvl3j8=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
53
vendor/github.com/googleapis/gax-go/v2/header.go
generated
vendored
Normal file
53
vendor/github.com/googleapis/gax-go/v2/header.go
generated
vendored
Normal file
@@ -0,0 +1,53 @@
|
||||
// Copyright 2018, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package gax
|
||||
|
||||
import "bytes"
|
||||
|
||||
// XGoogHeader is for use by the Google Cloud Libraries only.
|
||||
//
|
||||
// XGoogHeader formats key-value pairs.
|
||||
// The resulting string is suitable for x-goog-api-client header.
|
||||
func XGoogHeader(keyval ...string) string {
|
||||
if len(keyval) == 0 {
|
||||
return ""
|
||||
}
|
||||
if len(keyval)%2 != 0 {
|
||||
panic("gax.Header: odd argument count")
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
for i := 0; i < len(keyval); i += 2 {
|
||||
buf.WriteByte(' ')
|
||||
buf.WriteString(keyval[i])
|
||||
buf.WriteByte('/')
|
||||
buf.WriteString(keyval[i+1])
|
||||
}
|
||||
return buf.String()[1:]
|
||||
}
|
||||
48
vendor/github.com/googleapis/gax-go/v2/header_test.go
generated
vendored
Normal file
48
vendor/github.com/googleapis/gax-go/v2/header_test.go
generated
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
// Copyright 2018, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package gax
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestXGoogHeader(t *testing.T) {
|
||||
for _, tst := range []struct {
|
||||
kv []string
|
||||
want string
|
||||
}{
|
||||
{nil, ""},
|
||||
{[]string{"abc", "def"}, "abc/def"},
|
||||
{[]string{"abc", "def", "xyz", "123", "foo", ""}, "abc/def xyz/123 foo/"},
|
||||
} {
|
||||
got := XGoogHeader(tst.kv...)
|
||||
if got != tst.want {
|
||||
t.Errorf("Header(%q) = %q, want %q", tst.kv, got, tst.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
99
vendor/github.com/googleapis/gax-go/v2/invoke.go
generated
vendored
Normal file
99
vendor/github.com/googleapis/gax-go/v2/invoke.go
generated
vendored
Normal file
@@ -0,0 +1,99 @@
|
||||
// Copyright 2016, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package gax
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// APICall is a user defined call stub.
|
||||
type APICall func(context.Context, CallSettings) error
|
||||
|
||||
// Invoke calls the given APICall,
|
||||
// performing retries as specified by opts, if any.
|
||||
func Invoke(ctx context.Context, call APICall, opts ...CallOption) error {
|
||||
var settings CallSettings
|
||||
for _, opt := range opts {
|
||||
opt.Resolve(&settings)
|
||||
}
|
||||
return invoke(ctx, call, settings, Sleep)
|
||||
}
|
||||
|
||||
// Sleep is similar to time.Sleep, but it can be interrupted by ctx.Done() closing.
|
||||
// If interrupted, Sleep returns ctx.Err().
|
||||
func Sleep(ctx context.Context, d time.Duration) error {
|
||||
t := time.NewTimer(d)
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
t.Stop()
|
||||
return ctx.Err()
|
||||
case <-t.C:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
type sleeper func(ctx context.Context, d time.Duration) error
|
||||
|
||||
// invoke implements Invoke, taking an additional sleeper argument for testing.
|
||||
func invoke(ctx context.Context, call APICall, settings CallSettings, sp sleeper) error {
|
||||
var retryer Retryer
|
||||
for {
|
||||
err := call(ctx, settings)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
if settings.Retry == nil {
|
||||
return err
|
||||
}
|
||||
// Never retry permanent certificate errors. (e.x. if ca-certificates
|
||||
// are not installed). We should only make very few, targeted
|
||||
// exceptions: many (other) status=Unavailable should be retried, such
|
||||
// as if there's a network hiccup, or the internet goes out for a
|
||||
// minute. This is also why here we are doing string parsing instead of
|
||||
// simply making Unavailable a non-retried code elsewhere.
|
||||
if strings.Contains(err.Error(), "x509: certificate signed by unknown authority") {
|
||||
return err
|
||||
}
|
||||
if retryer == nil {
|
||||
if r := settings.Retry(); r != nil {
|
||||
retryer = r
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if d, ok := retryer.Retry(err); !ok {
|
||||
return err
|
||||
} else if err = sp(ctx, d); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
155
vendor/github.com/googleapis/gax-go/v2/invoke_test.go
generated
vendored
Normal file
155
vendor/github.com/googleapis/gax-go/v2/invoke_test.go
generated
vendored
Normal file
@@ -0,0 +1,155 @@
|
||||
// Copyright 2016, Google Inc.
|
||||
// All rights reserved.
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package gax
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
var canceledContext context.Context
|
||||
|
||||
func init() {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
canceledContext = ctx
|
||||
}
|
||||
|
||||
// recordSleeper is a test implementation of sleeper.
|
||||
type recordSleeper int
|
||||
|
||||
func (s *recordSleeper) sleep(ctx context.Context, _ time.Duration) error {
|
||||
*s++
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
type boolRetryer bool
|
||||
|
||||
func (r boolRetryer) Retry(err error) (time.Duration, bool) { return 0, bool(r) }
|
||||
|
||||
func TestInvokeSuccess(t *testing.T) {
|
||||
apiCall := func(context.Context, CallSettings) error { return nil }
|
||||
var sp recordSleeper
|
||||
err := invoke(context.Background(), apiCall, CallSettings{}, sp.sleep)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("found error %s, want nil", err)
|
||||
}
|
||||
if sp != 0 {
|
||||
t.Errorf("slept %d times, should not have slept since the call succeeded", int(sp))
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvokeNoRetry(t *testing.T) {
|
||||
apiErr := errors.New("foo error")
|
||||
apiCall := func(context.Context, CallSettings) error { return apiErr }
|
||||
var sp recordSleeper
|
||||
err := invoke(context.Background(), apiCall, CallSettings{}, sp.sleep)
|
||||
|
||||
if err != apiErr {
|
||||
t.Errorf("found error %s, want %s", err, apiErr)
|
||||
}
|
||||
if sp != 0 {
|
||||
t.Errorf("slept %d times, should not have slept since retry is not specified", int(sp))
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvokeNilRetry(t *testing.T) {
|
||||
apiErr := errors.New("foo error")
|
||||
apiCall := func(context.Context, CallSettings) error { return apiErr }
|
||||
var settings CallSettings
|
||||
WithRetry(func() Retryer { return nil }).Resolve(&settings)
|
||||
var sp recordSleeper
|
||||
err := invoke(context.Background(), apiCall, settings, sp.sleep)
|
||||
|
||||
if err != apiErr {
|
||||
t.Errorf("found error %s, want %s", err, apiErr)
|
||||
}
|
||||
if sp != 0 {
|
||||
t.Errorf("slept %d times, should not have slept since retry is not specified", int(sp))
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvokeNeverRetry(t *testing.T) {
|
||||
apiErr := errors.New("foo error")
|
||||
apiCall := func(context.Context, CallSettings) error { return apiErr }
|
||||
var settings CallSettings
|
||||
WithRetry(func() Retryer { return boolRetryer(false) }).Resolve(&settings)
|
||||
var sp recordSleeper
|
||||
err := invoke(context.Background(), apiCall, settings, sp.sleep)
|
||||
|
||||
if err != apiErr {
|
||||
t.Errorf("found error %s, want %s", err, apiErr)
|
||||
}
|
||||
if sp != 0 {
|
||||
t.Errorf("slept %d times, should not have slept since retry is not specified", int(sp))
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvokeRetry(t *testing.T) {
|
||||
const target = 3
|
||||
|
||||
retryNum := 0
|
||||
apiErr := errors.New("foo error")
|
||||
apiCall := func(context.Context, CallSettings) error {
|
||||
retryNum++
|
||||
if retryNum < target {
|
||||
return apiErr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
var settings CallSettings
|
||||
WithRetry(func() Retryer { return boolRetryer(true) }).Resolve(&settings)
|
||||
var sp recordSleeper
|
||||
err := invoke(context.Background(), apiCall, settings, sp.sleep)
|
||||
|
||||
if err != nil {
|
||||
t.Errorf("found error %s, want nil, call should have succeeded after %d tries", err, target)
|
||||
}
|
||||
if sp != target-1 {
|
||||
t.Errorf("retried %d times, want %d", int(sp), int(target-1))
|
||||
}
|
||||
}
|
||||
|
||||
func TestInvokeRetryTimeout(t *testing.T) {
|
||||
apiErr := errors.New("foo error")
|
||||
apiCall := func(context.Context, CallSettings) error { return apiErr }
|
||||
var settings CallSettings
|
||||
WithRetry(func() Retryer { return boolRetryer(true) }).Resolve(&settings)
|
||||
var sp recordSleeper
|
||||
|
||||
err := invoke(canceledContext, apiCall, settings, sp.sleep)
|
||||
|
||||
if err != context.Canceled {
|
||||
t.Errorf("found error %s, want %s", err, context.Canceled)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user