CLI support 45/4345/2
authorHuifeng Le <huifeng.le@intel.com>
Tue, 15 Jun 2021 04:18:36 +0000 (12:18 +0800)
committerHuifeng Le <huifeng.le@intel.com>
Tue, 15 Jun 2021 04:17:24 +0000 (04:17 +0000)
Add ewoctl to support configure overlay controller through command line.

Signed-off-by: Huifeng Le <huifeng.le@intel.com>
Change-Id: I2d9c9370f561e45a84c8f2bb72181ae8dfa32b32

17 files changed:
central-controller/src/scc/tools/ewoctl/Makefile [new file with mode: 0644]
central-controller/src/scc/tools/ewoctl/Readme.md [new file with mode: 0644]
central-controller/src/scc/tools/ewoctl/cmd/apply.go [new file with mode: 0644]
central-controller/src/scc/tools/ewoctl/cmd/config.go [new file with mode: 0644]
central-controller/src/scc/tools/ewoctl/cmd/delete.go [new file with mode: 0644]
central-controller/src/scc/tools/ewoctl/cmd/get.go [new file with mode: 0644]
central-controller/src/scc/tools/ewoctl/cmd/root.go [new file with mode: 0644]
central-controller/src/scc/tools/ewoctl/cmd/update.go [new file with mode: 0644]
central-controller/src/scc/tools/ewoctl/cmd/utils.go [new file with mode: 0644]
central-controller/src/scc/tools/ewoctl/ewoctl.go [new file with mode: 0644]
central-controller/src/scc/tools/ewoctl/examples/ewo-cfg.yaml [new file with mode: 0644]
central-controller/src/scc/tools/ewoctl/examples/overlay.yaml [new file with mode: 0644]
central-controller/src/scc/tools/ewoctl/examples/test.yaml [new file with mode: 0644]
central-controller/src/scc/tools/ewoctl/examples/test_template.yaml [new file with mode: 0644]
central-controller/src/scc/tools/ewoctl/examples/values.yaml [new file with mode: 0644]
central-controller/src/scc/tools/ewoctl/go.mod [new file with mode: 0644]
central-controller/src/scc/tools/ewoctl/go.sum [new file with mode: 0644]

diff --git a/central-controller/src/scc/tools/ewoctl/Makefile b/central-controller/src/scc/tools/ewoctl/Makefile
new file mode 100644 (file)
index 0000000..690ef3e
--- /dev/null
@@ -0,0 +1,26 @@
+# SPDX-License-Identifier: Apache-2.0
+# Copyright (c) 2020 Intel Corporation
+
+export GO111MODULE=on
+
+all: clean
+       CGO_ENABLED=0 GOOS=linux GOARCH=amd64 \
+       go build -o ../../../bin/ewoctl/ewoctl ./ewoctl.go
+
+build: clean test cover
+deploy: build
+
+.PHONY: test
+test: clean
+       @go test -race ./...
+
+format:
+       @go fmt ./...
+
+clean:
+       @rm -f ../../../bin/ewoctl/ewoctl coverage.html coverage.out
+
+.PHONY: cover
+cover:
+       @go test -race ./... -coverprofile=coverage.out
+       @go tool cover -html=coverage.out -o coverage.html
diff --git a/central-controller/src/scc/tools/ewoctl/Readme.md b/central-controller/src/scc/tools/ewoctl/Readme.md
new file mode 100644 (file)
index 0000000..2349cc6
--- /dev/null
@@ -0,0 +1,135 @@
+# SPDX-License-Identifier: Apache-2.0
+# Copyright (c) 2020 Intel Corporation
+
+#################################################################
+# EWOCTL - CLI for EWO
+#################################################################
+
+Ewoctl is command line tool for interacting with EWO.
+All commands take input a file. An input file can contain one or more resources.
+
+
+### Syntax for describing a resource
+
+```
+version: <domain-name>/<api-version>
+resourceContext:
+  anchor: <URI>
+metadata:
+   Name: <name>
+   Description: <text>
+   userData1: <text>
+   userData2: <text>
+spec:
+  <key>: <value>
+```
+
+### Example resource file
+
+```
+version: ewo/v1
+resourceContext:
+  anchor: overlays
+metadata:
+   Name: overlay1
+   Description: test
+   userData1: test1
+   userData2: test2
+
+---
+version: ewo/v1
+resourceContext:
+  anchor: overlays/overlay1/ipranges
+metadata:
+  name: iprange1
+  description: test
+  userData1: test1
+  userData2: test2
+spec:
+  subnet: 10.10.10.10
+  minIp: 0
+  maxIp: 255
+```
+
+### EWO CLI Commands
+
+1. Create Ewo Resources
+
+This command will apply the resources in the file. The user is responsible to ensuring the hierarchy of the resources.
+
+`$ ewoctl apply -f filename.yaml`
+
+2. Get Ewo Resources
+
+Get the resources in the input file. This command will use the metadata name in each of the resources in the file to get information about the resource.
+
+`$ ewoctl get -f filename.yaml`
+
+For getting information for one resource anchor can be provided as an arguement
+
+`$ ewoctl get <anchor>`
+
+`$ ewoctl get overlays/overlay1`
+
+3. Delete Ewo Resources
+
+Delete resources in the file. The ewoctl will start deleting resources in the reverse order than given in the file to maintain hierarchy. This command will use the metadata name in each of the resources in the file to delete the resource..
+
+`$ ewoctl delete -f filename.yaml`
+
+For deleting one resource anchor can be provided as an arguement
+
+`$ ewoctl delete <anchor>`
+
+`$ ewoctl delete overlays/overlay1`
+
+4. Update Ewo Resources
+
+This command will call update (PUT) for the resources in the file.
+
+`$ ewoctl update -f filename.yaml`
+
+
+### Running the ewoctl
+
+```
+* Make sure that the ewoctl is build. You can build it by issuing the 'make' command.
+Dir : $EWO_HOME/src/tools/ewoctl
+```
+* Then run the ewoctl by command:
+```
+./ewoctl --config ./examples/ewo-cfg.yaml apply -f ./examples/test.yaml
+
+```
+
+Here, ewo-cfg.yaml contains the config/port details of each of the microservices you are using.
+A sample configuration is :
+
+```
+  ewo:
+    host: localhost
+    port: 9015
+```
+
+### Running the ewoctl with template file
+
+```
+* Ewoctl supports template values in the input file. The example input file with this feature is
+examples/test_template.yaml. This file can be used with examples/values.yaml like below.
+```
+* Then run the ewoctl with values file:
+```
+ewoctl --config ./examples/ewo-cfg.yaml apply -f ./examples/test_template.yaml -v ./examples/values.yaml
+
+```
+
+### Running the ewoctl with token
+
+```
+* Ewoctl supports JWT tokens for interacting with EWO when EWO services are running with Istio Ingress and OAuth2 server.
+```
+* Then run the ewoctl with values file:
+```
+ewoctl --config ./examples/ewo-cfg.yaml apply -f ./examples/test.yaml -t "<token>"
+
+```
\ No newline at end of file
diff --git a/central-controller/src/scc/tools/ewoctl/cmd/apply.go b/central-controller/src/scc/tools/ewoctl/cmd/apply.go
new file mode 100644 (file)
index 0000000..d0f6199
--- /dev/null
@@ -0,0 +1,59 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (c) 2020 Intel Corporation
+
+package cmd
+
+import (
+       "fmt"
+
+       "github.com/spf13/cobra"
+)
+
+// applyCmd represents the apply command
+var applyCmd = &cobra.Command{
+       Use:   "apply",
+       Short: "apply(Post) the resources from input file or url(without body) from command line",
+       Run: func(cmd *cobra.Command, args []string) {
+               var c RestyClient
+               if len(token) > 0 {
+                       c = NewRestClientToken(token[0])
+               } else {
+                       c = NewRestClient()
+               }
+               if len(inputFiles) > 0 {
+                       resources := readResources()
+                       for _, res := range resources {
+                               if res.file != "" {
+                                       err := c.RestClientMultipartPost(res.anchor, res.body, res.file)
+                                       if err != nil && err.Error() != "Server Error" {
+                                               fmt.Println("Apply: ", res.anchor, "Error: ", err)
+                                               return
+                                       }
+                               } else if len(res.files) > 0 {
+                                       err := c.RestClientMultipartPostMultipleFiles(res.anchor, res.body, res.files)
+                                       if err != nil && err.Error() != "Server Error" {
+                                               fmt.Println("Apply: ", res.anchor, "Error: ", err)
+                                               return
+                                       }
+                               } else {
+                                       err := c.RestClientPost(res.anchor, res.body)
+                                       if err != nil && err.Error() != "Server Error" {
+                                               fmt.Println("Apply: ", res.anchor, "Error: ", err)
+                                               return
+                                       }
+                               }
+                       }
+               } else if len(args) >= 1 {
+                       c.RestClientPost(args[0], []byte{})
+               } else {
+                       fmt.Println("Error: No args ")
+               }
+       },
+}
+
+func init() {
+       rootCmd.AddCommand(applyCmd)
+       applyCmd.Flags().StringSliceVarP(&inputFiles, "filename", "f", []string{}, "Filename of the input file")
+       applyCmd.Flags().StringSliceVarP(&valuesFiles, "values", "v", []string{}, "Template Values to go with the input template file")
+       applyCmd.Flags().StringSliceVarP(&token, "token", "t", []string{}, "Token for EWO API")
+}
diff --git a/central-controller/src/scc/tools/ewoctl/cmd/config.go b/central-controller/src/scc/tools/ewoctl/cmd/config.go
new file mode 100644 (file)
index 0000000..008f290
--- /dev/null
@@ -0,0 +1,55 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (c) 2020 Intel Corporation
+
+package cmd
+
+import (
+       "fmt"
+       "os"
+       "strconv"
+)
+
+// Configurations exported
+type EwoConfigurations struct {
+       Ingress      ControllerConfigurations
+       Ewo          ControllerConfigurations
+}
+
+// ControllerConfigurations exported
+type ControllerConfigurations struct {
+       Port int
+       Host string
+}
+
+const baseUrl string = "scc/v1"
+const urlPrefix string = "http://"
+
+var Configurations EwoConfigurations
+
+// SetDefaultConfiguration default configuration if t
+func SetDefaultConfiguration() {
+       Configurations.Ewo.Host = "localhost"
+       Configurations.Ewo.Port = 9015
+}
+
+// GetIngressURL Url for Ingress
+func GetIngressURL() string {
+       if Configurations.Ingress.Host == "" || Configurations.Ingress.Port == 0 {
+               return ""
+       }
+       return urlPrefix + Configurations.Ingress.Host + ":" + strconv.Itoa(Configurations.Ingress.Port) + "/" + baseUrl
+}
+
+// GetEwoURL Url for Edge Wan Overlay Controller
+func GetEwoURL() string {
+       // If Ingress is available use that url
+       if s := GetIngressURL(); s != "" {
+               return s
+       }
+       if Configurations.Ewo.Host == "" || Configurations.Ewo.Port == 0 {
+               fmt.Println("Fatal: No Ewo Information in Config File")
+               // Exit executing
+               os.Exit(1)
+       }
+       return urlPrefix + Configurations.Ewo.Host + ":" + strconv.Itoa(Configurations.Ewo.Port) + "/" + baseUrl
+}
\ No newline at end of file
diff --git a/central-controller/src/scc/tools/ewoctl/cmd/delete.go b/central-controller/src/scc/tools/ewoctl/cmd/delete.go
new file mode 100644 (file)
index 0000000..917a439
--- /dev/null
@@ -0,0 +1,42 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (c) 2020 Intel Corporation
+
+package cmd
+
+import (
+       "fmt"
+
+       "github.com/spf13/cobra"
+)
+
+// deleteCmd represents the delete command
+var deleteCmd = &cobra.Command{
+       Use:   "delete",
+       Short: "Delete the resources from input file or url from command line",
+       Run: func(cmd *cobra.Command, args []string) {
+               var c RestyClient
+               if len(token) > 0 {
+                       c = NewRestClientToken(token[0])
+               } else {
+                       c = NewRestClient()
+               }
+               if len(inputFiles) > 0 {
+                       resources := readResources()
+                       for i := len(resources) - 1; i >= 0; i-- {
+                               res := resources[i]
+                               c.RestClientDelete(res.anchor, res.body)
+                       }
+               } else if len(args) >= 1 {
+                       c.RestClientDeleteAnchor(args[0])
+               } else {
+                       fmt.Println("Error: No args ")
+               }
+       },
+}
+
+func init() {
+       rootCmd.AddCommand(deleteCmd)
+       deleteCmd.Flags().StringSliceVarP(&inputFiles, "filename", "f", []string{}, "Filename of the input file")
+       deleteCmd.Flags().StringSliceVarP(&valuesFiles, "values", "v", []string{}, "Template Values to go with the input template file")
+       deleteCmd.Flags().StringSliceVarP(&token, "token", "t", []string{}, "Token for EWO API")
+}
diff --git a/central-controller/src/scc/tools/ewoctl/cmd/get.go b/central-controller/src/scc/tools/ewoctl/cmd/get.go
new file mode 100644 (file)
index 0000000..d6261fe
--- /dev/null
@@ -0,0 +1,41 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (c) 2020 Intel Corporation
+
+package cmd
+
+import (
+       "fmt"
+
+       "github.com/spf13/cobra"
+)
+
+// getCmd represents the get command
+var getCmd = &cobra.Command{
+       Use:   "get",
+       Short: "Get the resources from input file or url from command line",
+       Run: func(cmd *cobra.Command, args []string) {
+               var c RestyClient
+               if len(token) > 0 {
+                       c = NewRestClientToken(token[0])
+               } else {
+                       c = NewRestClient()
+               }
+               if len(inputFiles) > 0 {
+                       resources := readResources()
+                       for _, res := range resources {
+                               c.RestClientGet(res.anchor, res.body)
+                       }
+               } else if len(args) >= 1 {
+                       c.RestClientGetAnchor(args[0])
+               } else {
+                       fmt.Println("Error: No args ")
+               }
+       },
+}
+
+func init() {
+       rootCmd.AddCommand(getCmd)
+       getCmd.Flags().StringSliceVarP(&inputFiles, "filename", "f", []string{}, "Filename of the input file")
+       getCmd.Flags().StringSliceVarP(&valuesFiles, "values", "v", []string{}, "Template Values to go with the input template file")
+       getCmd.Flags().StringSliceVarP(&token, "token", "t", []string{}, "Token for EWO API")
+}
diff --git a/central-controller/src/scc/tools/ewoctl/cmd/root.go b/central-controller/src/scc/tools/ewoctl/cmd/root.go
new file mode 100644 (file)
index 0000000..756c69e
--- /dev/null
@@ -0,0 +1,76 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (c) 2020 Intel Corporation
+
+package cmd
+
+import (
+       "fmt"
+       "os"
+
+       "github.com/spf13/cobra"
+
+       homedir "github.com/mitchellh/go-homedir"
+       "github.com/spf13/viper"
+)
+
+var cfgFile string
+
+// rootCmd represents the base command when called without any subcommands
+var rootCmd = &cobra.Command{
+       Use:   "ewo",
+       Short: "Ewoctl - CLI for EWO",
+       // Uncomment the following line if your bare application
+       // has an action associated with it:
+       Run: func(cmd *cobra.Command, args []string) { fmt.Println("ewoctl <command> -f file") },
+}
+
+// Execute adds all child commands to the root command and sets flags appropriately.
+// This is called by main.main(). It only needs to happen once to the rootCmd.
+func Execute() {
+       if err := rootCmd.Execute(); err != nil {
+               fmt.Println(err)
+               os.Exit(1)
+       }
+}
+
+func init() {
+       cobra.OnInitialize(initConfig)
+       rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.ewo.yaml)")
+
+       // Cobra also supports local flags, which will only run
+       // when this action is called directly.
+       rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
+}
+
+// initConfig reads in config file and ENV variables if set.
+func initConfig() {
+
+       if cfgFile != "" {
+               // Use config file from the flag.
+               viper.SetConfigFile(cfgFile)
+       } else {
+               // Find home directory.
+               home, err := homedir.Dir()
+               if err != nil {
+                       fmt.Println(err)
+                       os.Exit(1)
+               }
+               // Search config in home directory with name ".ewo" (without extension).
+               viper.AddConfigPath(home)
+               viper.SetConfigName(".ewo")
+       }
+
+       viper.AutomaticEnv() // read in environment variables that match
+
+       // If a config file is found, read it in.
+       if err := viper.ReadInConfig(); err == nil {
+               fmt.Println("Using config file:", viper.ConfigFileUsed())
+               err := viper.Unmarshal(&Configurations)
+               if err != nil {
+                       fmt.Printf("Unable to decode into struct, %v", err)
+               }
+       } else {
+               fmt.Println("Warning: No Configuration File found. Using defaults")
+               SetDefaultConfiguration()
+       }
+}
diff --git a/central-controller/src/scc/tools/ewoctl/cmd/update.go b/central-controller/src/scc/tools/ewoctl/cmd/update.go
new file mode 100644 (file)
index 0000000..5a6acb9
--- /dev/null
@@ -0,0 +1,54 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (c) 2020 Intel Corporation
+
+package cmd
+
+import (
+       "fmt"
+
+       "github.com/spf13/cobra"
+)
+
+// updateCmd represents the update command
+var updateCmd = &cobra.Command{
+       Use:   "update",
+       Short: "update(Put) the resources from input file or url(without body) from command line",
+       Run: func(cmd *cobra.Command, args []string) {
+               var c RestyClient
+               if len(token) > 0 {
+                       c = NewRestClientToken(token[0])
+               } else {
+                       c = NewRestClient()
+               }
+               if len(inputFiles) > 0 {
+                       resources := readResources()
+                       for _, res := range resources {
+                               if res.file != "" {
+                                       err := c.RestClientMultipartPut(res.anchor, res.body, res.file)
+                                       if err != nil && err.Error() != "Server Error" {
+                                               fmt.Println("Update: ", res.anchor, "Error: ", err)
+                                       }
+                               } else if len(res.files) > 0 {
+                                       err := c.RestClientMultipartPutMultipleFiles(res.anchor, res.body, res.files)
+                                       if err != nil && err.Error() != "Server Error" {
+                                               fmt.Println("Update: ", res.anchor, "Error: ", err)
+                                       }
+                               } else {
+                                       err := c.RestClientPut(res.anchor, res.body)
+                                       if err != nil && err.Error() != "Server Error" {
+                                               fmt.Println("Update: ", res.anchor, "Error: ", err)
+                                       }
+                               }
+                       }
+               } else {
+                       fmt.Println("Error: No args ")
+               }
+       },
+}
+
+func init() {
+       rootCmd.AddCommand(updateCmd)
+       updateCmd.Flags().StringSliceVarP(&inputFiles, "filename", "f", []string{}, "Filename of the input file")
+       updateCmd.Flags().StringSliceVarP(&valuesFiles, "values", "v", []string{}, "Template Values to go with the input template file")
+       updateCmd.Flags().StringSliceVarP(&token, "token", "t", []string{}, "Token for EWO API")
+}
diff --git a/central-controller/src/scc/tools/ewoctl/cmd/utils.go b/central-controller/src/scc/tools/ewoctl/cmd/utils.go
new file mode 100644 (file)
index 0000000..870d1a1
--- /dev/null
@@ -0,0 +1,443 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (c) 2020 Intel Corporation
+
+package cmd
+
+import (
+       "bytes"
+       "encoding/json"
+       "fmt"
+       "io"
+       "io/ioutil"
+       neturl "net/url"
+       "os"
+       "strings"
+
+       "text/template"
+
+       "github.com/go-resty/resty/v2"
+//     "github.com/mitchellh/mapstructure"
+       pkgerrors "github.com/pkg/errors"
+       "gopkg.in/yaml.v3"
+)
+
+var inputFiles []string
+var valuesFiles []string
+var token []string
+
+type ResourceContext struct {
+       Anchor string `json:"anchor" yaml:"anchor"`
+}
+
+type Metadata struct {
+       Name        string `yaml:"name" json:"name"`
+       Description string `yaml:"description,omitempty" json:"description,omitempty"`
+       UserData1   string `yaml:"userData1,omitempty" json:"userData1,omitempty"`
+       UserData2   string `yaml:"userData2,omitempty" json:"userData2,omitempty"`
+}
+
+type ewoRes struct {
+       Version string                 `yaml:"version" json:"version"`
+       Context ResourceContext        `yaml:"resourceContext" json:"resourceContext"`
+       Meta    Metadata               `yaml:"metadata" json:"metadata"`
+       Spec    map[string]interface{} `yaml:"spec,omitempty" json:"spec,omitempty"`
+       File    string                 `yaml:"file,omitempty" json:"file,omitempty"`
+       Files   []string               `yaml:"files,omitempty" json:"files,omitempty"`
+}
+
+type ewoBody struct {
+       Meta  Metadata               `json:"metadata,omitempty"`
+       Spec  map[string]interface{} `json:"spec,omitempty"`
+}
+
+type Resources struct {
+       anchor string
+       body   []byte
+       file   string
+       files  []string
+}
+
+// RestyClient to use with CLI
+type RestyClient struct {
+       client *resty.Client
+}
+
+var Client RestyClient
+
+// NewRestClient returns a rest client
+func NewRestClient() RestyClient {
+       // Create a Resty Client
+       Client.client = resty.New()
+       // Registering global Error object structure for JSON/XML request
+       //Client.client.SetError(&Error{})
+       return Client
+}
+
+// NewRestClientToken returns a rest client with token
+func NewRestClientToken(token string) RestyClient {
+       // Create a Resty Client
+       Client.client = resty.New()
+       // Bearer Auth Token for all request
+       Client.client.SetAuthToken(token)
+       // Registering global Error object structure for JSON/XML request
+       //Client.client.SetError(&Error{})
+       return Client
+}
+
+// readResources reads all the resources in the file provided
+func readResources() []Resources {
+       // TODO: Remove Assumption only one file
+       // Open file and Parse to get all resources
+       var resources []Resources
+       var buf bytes.Buffer
+
+       if len(valuesFiles) > 0 {
+               //Apply template
+               v, err := os.Open(valuesFiles[0])
+               defer v.Close()
+               if err != nil {
+                       fmt.Println("Error reading file", "error", err, "filename", valuesFiles[0])
+                       return []Resources{}
+               }
+               valDec := yaml.NewDecoder(v)
+               var mapDoc map[string]string
+               if valDec.Decode(&mapDoc) != nil {
+                       fmt.Println("Values file format incorrect:", "error", err, "filename", valuesFiles[0])
+                       return []Resources{}
+               }
+               // Templatize
+               t, err := template.ParseFiles(inputFiles[0])
+               if err != nil {
+                       fmt.Println("Error reading file", "error", err, "filename", inputFiles[0])
+                       return []Resources{}
+               }
+               err = t.Execute(&buf, mapDoc)
+               if err != nil {
+                       fmt.Println("execute: ", err)
+                       return []Resources{}
+               }
+       } else {
+               f, err := os.Open(inputFiles[0])
+               defer f.Close()
+               if err != nil {
+                       fmt.Println("Error reading file", "error", err, "filename", inputFiles[0])
+                       return []Resources{}
+               }
+               io.Copy(&buf, f)
+       }
+
+       dec := yaml.NewDecoder(&buf)
+       // Iterate through all resources in the file
+       for {
+               var doc ewoRes
+               if err := dec.Decode(&doc); err != nil {
+                       if err.Error() != "EOF" {
+                               fmt.Println("Invalid input Yaml! Exiting..", err)
+                               // Exit executing
+                               os.Exit(1)
+                       }
+                       break
+               }
+               body := &ewoBody{Meta: doc.Meta, Spec: doc.Spec}
+               jsonBody, err := json.Marshal(body)
+               if err != nil {
+                       fmt.Println("Invalid input Yaml! Exiting..", err)
+                       // Exit executing
+                       os.Exit(1)
+               }
+               var res Resources
+               if doc.File != "" {
+                       res = Resources{anchor: doc.Context.Anchor, body: jsonBody, file: doc.File}
+               } else if len(doc.Files) > 0 {
+                       res = Resources{anchor: doc.Context.Anchor, body: jsonBody, files: doc.Files}
+               } else {
+                       res = Resources{anchor: doc.Context.Anchor, body: jsonBody}
+               }
+               resources = append(resources, res)
+       }
+       return resources
+}
+
+//RestClientApply to post to server no multipart
+func (r RestyClient) RestClientApply(anchor string, body []byte, put bool) error {
+       var resp *resty.Response
+       var err error
+       var url string
+
+       if put {
+               if anchor, err = getUpdateUrl(anchor, body); err != nil {
+                       return err
+               }
+               if url, err = GetURL(anchor); err != nil {
+                       return err
+               }
+               // Put JSON string
+               resp, err = r.client.R().
+                       SetHeader("Content-Type", "application/json").
+                       SetBody(body).
+                       Put(url)
+       } else {
+               if url, err = GetURL(anchor); err != nil {
+                       return err
+               }
+               // Post JSON string
+               resp, err = r.client.R().
+                       SetHeader("Content-Type", "application/json").
+                       SetBody(body).
+                       Post(url)
+       }
+       if err != nil {
+               fmt.Println(err)
+               return err
+       }
+       if put {
+               printOutput(url, "PUT", resp)
+       } else {
+               printOutput(url, "POST", resp)
+       }
+       if resp.StatusCode() >= 200 && resp.StatusCode() <= 299 {
+               return nil
+       }
+       return pkgerrors.Errorf("Server Error")
+}
+
+//RestClientPut to post to server no multipart
+func (r RestyClient) RestClientPut(anchor string, body []byte) error {
+       if anchor == "" {
+               return pkgerrors.Errorf("Anchor can't be empty")
+       }
+       return r.RestClientApply(anchor, body, true)
+}
+
+//RestClientPost to post to server no multipart
+func (r RestyClient) RestClientPost(anchor string, body []byte) error {
+       return r.RestClientApply(anchor, body, false)
+}
+
+//RestClientMultipartApply to post to server with multipart
+func (r RestyClient) RestClientMultipartApply(anchor string, body []byte, file string, put bool) error {
+       var resp *resty.Response
+       var err error
+       var url string
+
+       // Read file for multipart
+       f, err := ioutil.ReadFile(file)
+       if err != nil {
+               fmt.Println("Error reading file", "error", err, "filename", file)
+               return err
+       }
+
+       // Multipart Post
+       formParams := neturl.Values{}
+       formParams.Add("metadata", string(body))
+       if put {
+               if anchor, err = getUpdateUrl(anchor, body); err != nil {
+                       return err
+               }
+               if url, err = GetURL(anchor); err != nil {
+                       return err
+               }
+               resp, err = r.client.R().
+                       SetFileReader("file", "filename", bytes.NewReader(f)).
+                       SetFormDataFromValues(formParams).
+                       Put(url)
+       } else {
+               if url, err = GetURL(anchor); err != nil {
+                       return err
+               }
+               resp, err = r.client.R().
+                       SetFileReader("file", "filename", bytes.NewReader(f)).
+                       SetFormDataFromValues(formParams).
+                       Post(url)
+       }
+       if err != nil {
+               fmt.Println(err)
+               return err
+       }
+       if put {
+               printOutput(url, "PUT", resp)
+       } else {
+               printOutput(url, "POST", resp)
+       }
+       if resp.StatusCode() >= 201 && resp.StatusCode() <= 299 {
+               return nil
+       }
+       return pkgerrors.Errorf("Server Error")
+}
+
+//RestClientMultipartPut to post to server with multipart
+func (r RestyClient) RestClientMultipartPut(anchor string, body []byte, file string) error {
+       return r.RestClientMultipartApply(anchor, body, file, true)
+}
+
+//RestClientMultipartPost to post to server with multipart
+func (r RestyClient) RestClientMultipartPost(anchor string, body []byte, file string) error {
+       return r.RestClientMultipartApply(anchor, body, file, false)
+}
+
+func getFile(file string) ([]byte, string, error) {
+       // Read file for multipart
+       f, err := ioutil.ReadFile(file)
+       if err != nil {
+               fmt.Println("Error reading file", "error", err, "filename", file)
+               return []byte{}, "", err
+       }
+       // Extract filename
+       s := strings.TrimSuffix(file, "/")
+       s1 := strings.Split(s, "/")
+       name := s1[len(s1)-1]
+       return f, name, nil
+}
+
+//RestClientMultipartApplyMultipleFiles to post to server with multipart
+func (r RestyClient) RestClientMultipartApplyMultipleFiles(anchor string, body []byte, files []string, put bool) error {
+       var f []byte
+       var name string
+       var err error
+       var url string
+       var resp *resty.Response
+
+       req := r.client.R()
+       // Multipart Post
+       formParams := neturl.Values{}
+       formParams.Add("metadata", string(body))
+       // Add all files in the list
+       for _, file := range files {
+               f, name, err = getFile(file)
+               if err != nil {
+                       return err
+               }
+               req = req.
+                       SetFileReader("files", name, bytes.NewReader(f))
+       }
+       if put {
+               if anchor, err = getUpdateUrl(anchor, body); err != nil {
+                       return err
+               }
+               if url, err = GetURL(anchor); err != nil {
+                       return err
+               }
+               resp, err = req.
+                       SetFormDataFromValues(formParams).
+                       Put(url)
+       } else {
+               if url, err = GetURL(anchor); err != nil {
+                       return err
+               }
+               resp, err = req.
+                       SetFormDataFromValues(formParams).
+                       Post(url)
+       }
+       if err != nil {
+               fmt.Println(err)
+               return err
+       }
+       if put {
+               printOutput(url, "PUT", resp)
+       } else {
+               printOutput(url, "POST", resp)
+       }
+       if resp.StatusCode() >= 200 && resp.StatusCode() <= 299 {
+               return nil
+       }
+       return pkgerrors.Errorf("Server Error")
+}
+
+//RestClientMultipartPutMultipleFiles to post to server with multipart
+func (r RestyClient) RestClientMultipartPutMultipleFiles(anchor string, body []byte, files []string) error {
+       return r.RestClientMultipartApplyMultipleFiles(anchor, body, files, true)
+}
+
+//RestClientMultipartPostMultipleFiles to post to server with multipart
+func (r RestyClient) RestClientMultipartPostMultipleFiles(anchor string, body []byte, files []string) error {
+       return r.RestClientMultipartApplyMultipleFiles(anchor, body, files, false)
+}
+
+// RestClientGetAnchor returns get data from anchor
+func (r RestyClient) RestClientGetAnchor(anchor string) error {
+       url, err := GetURL(anchor)
+       if err != nil {
+               return err
+       }
+       resp, err := r.client.R().
+               Get(url)
+       if err != nil {
+               fmt.Println(err)
+               return err
+       }
+       printOutput(url, "GET", resp)
+       return nil
+}
+
+func getUpdateUrl(anchor string, body []byte) (string, error) {
+       var e ewoBody
+       err := json.Unmarshal(body, &e)
+       if err != nil {
+               fmt.Println(err)
+               return "", err
+       }
+       if e.Meta.Name != "" {
+               anchor = anchor + "/" + e.Meta.Name
+       }
+       return anchor, nil
+}
+
+// RestClientGet gets resource
+func (r RestyClient) RestClientGet(anchor string, body []byte) error {
+       if anchor == "" {
+               return pkgerrors.Errorf("Anchor can't be empty")
+       }
+       c, err := getUpdateUrl(anchor, body)
+       if err != nil {
+               return err
+       }
+       return r.RestClientGetAnchor(c)
+}
+
+// RestClientDeleteAnchor returns all resource in the input file
+func (r RestyClient) RestClientDeleteAnchor(anchor string) error {
+       url, err := GetURL(anchor)
+       if err != nil {
+               return err
+       }
+       resp, err := r.client.R().Delete(url)
+       if err != nil {
+               fmt.Println(err)
+               return err
+       }
+       printOutput(url, "DELETE", resp)
+       return nil
+}
+
+// RestClientDelete calls rest delete command
+func (r RestyClient) RestClientDelete(anchor string, body []byte) error {
+       if anchor == "" {
+               return pkgerrors.Errorf("Anchor can't be empty")
+       }
+       c, err := getUpdateUrl(anchor, body)
+       if err != nil {
+               return err
+       }
+       return r.RestClientDeleteAnchor(c)
+}
+
+// GetURL reads the configuration file to get URL
+func GetURL(anchor string) (string, error) {
+       var baseUrl string
+       s := strings.Split(anchor, "/")
+       if len(s) < 1 {
+               return "", fmt.Errorf("Invalid Anchor: %s", s)
+       }
+
+       baseUrl = GetEwoURL()
+       return (baseUrl + "/" + anchor), nil
+}
+
+func printOutput(url, op string, resp *resty.Response) {
+       fmt.Println("---")
+       fmt.Println(op, " --> URL:", url)
+       fmt.Println("Response Code:", resp.StatusCode())
+       if len(resp.Body()) > 0 {
+               fmt.Println("Response:", resp)
+       }
+}
diff --git a/central-controller/src/scc/tools/ewoctl/ewoctl.go b/central-controller/src/scc/tools/ewoctl/ewoctl.go
new file mode 100644 (file)
index 0000000..eda057d
--- /dev/null
@@ -0,0 +1,10 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright (c) 2020 Intel Corporation
+
+package main
+
+import "tools/ewoctl/cmd"
+
+func main() {
+       cmd.Execute()
+}
diff --git a/central-controller/src/scc/tools/ewoctl/examples/ewo-cfg.yaml b/central-controller/src/scc/tools/ewoctl/examples/ewo-cfg.yaml
new file mode 100644 (file)
index 0000000..6362204
--- /dev/null
@@ -0,0 +1,6 @@
+# SPDX-License-Identifier: Apache-2.0\r
+# Copyright (c) 2020 Intel Corporation\r
+\r
+  ewo:\r
+    host: localhost\r
+    port: 9015
\ No newline at end of file
diff --git a/central-controller/src/scc/tools/ewoctl/examples/overlay.yaml b/central-controller/src/scc/tools/ewoctl/examples/overlay.yaml
new file mode 100644 (file)
index 0000000..cb8e7b6
--- /dev/null
@@ -0,0 +1,23 @@
+# SPDX-License-Identifier: Apache-2.0\r
+# Copyright (c) 2020 Intel Corporation\r
+\r
+# creating overlay1\r
+version: ewo/v1\r
+resourceContext:\r
+  anchor: overlays\r
+metadata:\r
+  name: overlay1\r
+  description: \r
+  userData1: \r
+  userData2: \r
+\r
+---\r
+# creating overlay2\r
+version: ewo/v1\r
+resourceContext:\r
+  anchor: overlays\r
+metadata:\r
+  name: overlay2\r
+  description: \r
+  userData1: \r
+  userData2: 
\ No newline at end of file
diff --git a/central-controller/src/scc/tools/ewoctl/examples/test.yaml b/central-controller/src/scc/tools/ewoctl/examples/test.yaml
new file mode 100644 (file)
index 0000000..dffcb5a
--- /dev/null
@@ -0,0 +1,53 @@
+# SPDX-License-Identifier: Apache-2.0
+# Copyright (c) 2020 Intel Corporation
+
+# creating overlay1
+version: ewo/v1
+resourceContext:
+  anchor: overlays
+metadata:
+  name: overlay1
+  description: 
+  userData1: 
+  userData2: 
+
+---
+# creating proposal
+version: ewo/v1
+resourceContext:
+  anchor: overlays/overlay1/proposals
+metadata:
+  name: proposal1
+  description: 
+  userData1: 
+  userData2: 
+spec:
+  encryption: aes256
+  hash: sha256
+  dhGroup: modp4096
+
+---
+# creating iprange
+version: ewo/v1
+resourceContext:
+  anchor: overlays/overlay1/ipranges
+metadata:
+  name: iprange1
+  description: 
+  userData1: 
+  userData2: 
+spec:
+  subnet: 192.168.0.2
+  minIp: 10
+  maxIp: 100
+
+---
+# creating certificate
+version: ewo/v1
+resourceContext:
+  anchor: overlays/overlay1/certificates
+metadata:
+  name: device1
+  description: 
+  userData1: 
+  userData2: 
\ No newline at end of file
diff --git a/central-controller/src/scc/tools/ewoctl/examples/test_template.yaml b/central-controller/src/scc/tools/ewoctl/examples/test_template.yaml
new file mode 100644 (file)
index 0000000..9c2e8f5
--- /dev/null
@@ -0,0 +1,56 @@
+# SPDX-License-Identifier: Apache-2.0
+# Copyright (c) 2020 Intel Corporation
+
+# SPDX-License-Identifier: Apache-2.0
+# Copyright (c) 2020 Intel Corporation
+
+# creating overlay1
+version: ewo/v1
+resourceContext:
+  anchor: overlays
+metadata:
+  name: {{.OverlayName}}
+  description: 
+  userData1: 
+  userData2: 
+
+---
+# creating proposal
+version: ewo/v1
+resourceContext:
+  anchor: overlays/{{.OverlayName}}/proposals
+metadata:
+  name: {{.ProposalName}}
+  description: 
+  userData1: 
+  userData2: 
+spec:
+  encryption: aes256
+  hash: sha256
+  dhGroup: modp4096
+
+---
+# creating iprange
+version: ewo/v1
+resourceContext:
+  anchor: overlays/{{.OverlayName}}/ipranges
+metadata:
+  name: {{.IPRangelName}}
+  description: 
+  userData1: 
+  userData2: 
+spec:
+  subnet: 192.168.0.2
+  minIp: 10
+  maxIp: 100
+
+---
+# creating certificate
+version: ewo/v1
+resourceContext:
+  anchor: overlays/{{.OverlayName}}/certificates
+metadata:
+  name: {{.DeviceName}}
+  description: 
+  userData1: 
+  userData2: 
\ No newline at end of file
diff --git a/central-controller/src/scc/tools/ewoctl/examples/values.yaml b/central-controller/src/scc/tools/ewoctl/examples/values.yaml
new file mode 100644 (file)
index 0000000..4439e61
--- /dev/null
@@ -0,0 +1,7 @@
+# SPDX-License-Identifier: Apache-2.0\r
+# Copyright (c) 2020 Intel Corporation\r
+\r
+OverlayName: overlay1\r
+ProposalName: proposal1\r
+IPRangelName: iprange1\r
+DeviceName: device1
\ No newline at end of file
diff --git a/central-controller/src/scc/tools/ewoctl/go.mod b/central-controller/src/scc/tools/ewoctl/go.mod
new file mode 100644 (file)
index 0000000..077c5a5
--- /dev/null
@@ -0,0 +1,13 @@
+module tools/ewoctl
+
+go 1.14
+
+require (
+       github.com/go-resty/resty/v2 v2.3.0
+       github.com/mitchellh/go-homedir v1.1.0
+       github.com/mitchellh/mapstructure v1.1.2
+       github.com/pkg/errors v0.8.1
+       github.com/spf13/cobra v1.0.0
+       github.com/spf13/viper v1.7.1
+       gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776
+)
diff --git a/central-controller/src/scc/tools/ewoctl/go.sum b/central-controller/src/scc/tools/ewoctl/go.sum
new file mode 100644 (file)
index 0000000..e7d3496
--- /dev/null
@@ -0,0 +1,329 @@
+cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
+cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
+cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU=
+cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY=
+cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc=
+cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0=
+cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
+cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
+cloud.google.com/go/firestore v1.1.0/go.mod h1:ulACoGHTpvq5r8rxGJ4ddJZBZqakUQqClKRT5SZwBmk=
+cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
+cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
+dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
+github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
+github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
+github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
+github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
+github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
+github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
+github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
+github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
+github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
+github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
+github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
+github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
+github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
+github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
+github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
+github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
+github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
+github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
+github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
+github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
+github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
+github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
+github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
+github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no=
+github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
+github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
+github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
+github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
+github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
+github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
+github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
+github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
+github.com/go-resty/resty/v2 v2.3.0 h1:JOOeAvjSlapTT92p8xiS19Zxev1neGikoHsXJeOq8So=
+github.com/go-resty/resty/v2 v2.3.0/go.mod h1:UpN9CgLZNsv4e9XG50UU8xdI0F43UQ4HmxLBDwaroHU=
+github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
+github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
+github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
+github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
+github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
+github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
+github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y=
+github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
+github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
+github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
+github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
+github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
+github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
+github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
+github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
+github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
+github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
+github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
+github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
+github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
+github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
+github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
+github.com/hashicorp/consul/api v1.1.0/go.mod h1:VmuI/Lkw1nC05EYQWNKwWGbkg+FbDBtguAZLlVdkD9Q=
+github.com/hashicorp/consul/sdk v0.1.1/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8=
+github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
+github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
+github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
+github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
+github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
+github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
+github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
+github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
+github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
+github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
+github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
+github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
+github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
+github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ=
+github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
+github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc=
+github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
+github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
+github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
+github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
+github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
+github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
+github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
+github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
+github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
+github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
+github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
+github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
+github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
+github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
+github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
+github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
+github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
+github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
+github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
+github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
+github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
+github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
+github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
+github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
+github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
+github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
+github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
+github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg=
+github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
+github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
+github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
+github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
+github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
+github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
+github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
+github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
+github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
+github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
+github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
+github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
+github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
+github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
+github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso=
+github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
+github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
+github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
+github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
+github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
+github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
+github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
+github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
+github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
+github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
+github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
+github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
+github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
+github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
+github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
+github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
+github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
+github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
+github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
+github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
+github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
+github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
+github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
+github.com/spf13/cobra v1.0.0 h1:6m/oheQuQ13N9ks4hubMG6BnvwOeaJrqSPLahSnczz8=
+github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
+github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
+github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
+github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
+github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
+github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
+github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk=
+github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
+github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
+github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
+github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
+github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
+github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
+github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
+github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
+github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
+github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
+go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
+go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
+go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
+go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
+go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
+go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
+golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
+golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
+golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
+golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
+golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
+golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek=
+golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
+golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
+golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
+golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
+golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
+golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
+golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
+golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
+golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
+golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
+golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
+golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
+golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks=
+golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
+golang.org/x/net v0.0.0-20200513185701-a91f0712d120 h1:EZ3cVSzKOlJxAd8e8YAJ7no8nNypTxexh/YE/xW3ZEY=
+golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
+golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
+golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
+golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884=
+golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
+golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
+golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
+golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
+golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
+golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
+golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
+golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
+golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc=
+golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/tools v0.0.0-20191112195655-aa38f8e97acc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
+golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
+google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
+google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg=
+google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI=
+google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
+google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
+google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0=
+google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
+google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
+google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
+google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8=
+google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc=
+google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
+google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
+google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
+google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
+gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
+gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
+gopkg.in/ini.v1 v1.51.0 h1:AQvPpx3LzTDM0AjnIRlVFwFFGC+npRopjZxLJj6gdno=
+gopkg.in/ini.v1 v1.51.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
+gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
+gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
+gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
+gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776 h1:tQIYjPdBoyREyB9XMu+nnTclpTYkz2zFM+lzLJFO4gQ=
+gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
+honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
+rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=