From 9757e4a8194315778cc0b14b6eb4b44455f5679d Mon Sep 17 00:00:00 2001 From: Edan Schwartz Date: Fri, 22 Nov 2019 16:33:17 -0600 Subject: [PATCH] Add `dce auth`; refactor config mgmt, refactor tests Stuff going on here - Add `dce auth` command - pops you out to a browser at the `/auth` endpoint that @joshmarsh is working on - see #33 - Remove dce.yml configs we aren't using - Configure a AWS creds provider that will load creds from the auth token, but fallback to env vars or the `~/.aws/credentials` file - Run `dce auth` before any command, if we don't have any valid creds in our chain - Refactor how to load and save our config YAML, so it's easier to work with from inside multiple commands - Remove viper dependency (wasn't really being used) - Some other cleanup/refactor around init logic, to get this all wired up properly - I'll try to point these out in the code - New setup for integration tests - These test call our cobra CLI programmatically. For some reasons: - Lets us use debugger to run through tests - Don't need to rebuild for every test run - Can still use mocks at boundaries (eg. mock out web browser call) - I'm hoping this new testing pattern makes it easier to write tests for CLI commands going forward. --- .gitignore | 4 +- cmd/accounts.go | 8 +- cmd/auth.go | 8 +- cmd/init.go | 2 +- cmd/leases.go | 10 +- cmd/root.go | 144 +++++++++++-------- cmd/system.go | 2 +- cmd/usage.go | 2 +- configs/config.go | 20 +-- go.mod | 18 ++- go.sum | 98 ++++++++----- internal/util/api.go | 63 ++++---- internal/util/filesystem.go | 46 ++++-- internal/util/session.go | 96 +++++++++++++ internal/util/util.go | 47 +++--- mocks/Authenticater.go | 6 +- mocks/FileSystemer.go | 37 ++++- mocks/Initer.go | 6 +- pkg/service/auth.go | 47 +++++- pkg/service/init.go | 48 +++---- pkg/service/service.go | 4 +- tests/functional/initialization_test.go | 140 ------------------ tests/integration/auth_test.go | 72 ++++++++++ tests/integration/init_test.go | 183 ++++++++++++++++++++++++ tests/integration/mock_prompter.go | 73 ++++++++++ tests/integration/util.go | 131 +++++++++++++++++ tests/unit/auth_test.go | 33 ----- tests/unit/init_test.go | 101 ------------- 28 files changed, 945 insertions(+), 504 deletions(-) create mode 100644 internal/util/session.go delete mode 100644 tests/functional/initialization_test.go create mode 100644 tests/integration/auth_test.go create mode 100644 tests/integration/init_test.go create mode 100644 tests/integration/mock_prompter.go create mode 100644 tests/integration/util.go delete mode 100644 tests/unit/auth_test.go delete mode 100644 tests/unit/init_test.go diff --git a/.gitignore b/.gitignore index 2247df9..2d164bd 100644 --- a/.gitignore +++ b/.gitignore @@ -51,4 +51,6 @@ override.tf.json # Other stuff *.zip -junit-report/ \ No newline at end of file +junit-report/ + +dce-cli \ No newline at end of file diff --git a/cmd/accounts.go b/cmd/accounts.go index 238f0e4..28a87c8 100644 --- a/cmd/accounts.go +++ b/cmd/accounts.go @@ -33,7 +33,7 @@ var accountsDescribeCmd = &cobra.Command{ Short: "describe an account", Args: cobra.ExactValidArgs(1), Run: func(cmd *cobra.Command, args []string) { - service.GetAccount(args[0]) + Service.GetAccount(args[0]) }, } @@ -42,7 +42,7 @@ var accountsListCmd = &cobra.Command{ Short: "list accounts", Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { - service.ListAccounts() + Service.ListAccounts() }, } @@ -51,7 +51,7 @@ var accountsAddCmd = &cobra.Command{ Short: "Add an account to the accounts pool", Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { - service.AddAccount(accountID, adminRoleARN) + Service.AddAccount(accountID, adminRoleARN) }, } @@ -60,6 +60,6 @@ var accountsRemoveCmd = &cobra.Command{ Short: "Remove an account from the accounts pool.", Args: cobra.ExactValidArgs(1), Run: func(cmd *cobra.Command, args []string) { - service.RemoveAccount(args[0]) + Service.RemoveAccount(args[0]) }, } diff --git a/cmd/auth.go b/cmd/auth.go index f2d0753..c1b2165 100644 --- a/cmd/auth.go +++ b/cmd/auth.go @@ -4,17 +4,15 @@ import ( "github.com/spf13/cobra" ) -var authUrl string - func init() { RootCmd.AddCommand(authCmd) - authCmd.Flags().StringVarP(&authUrl, "url-override", "u", "", "Override the DCE login url") + authCmd.Flags() } var authCmd = &cobra.Command{ Use: "auth", Short: "Login to dce", - Run: func(cmd *cobra.Command, args []string) { - service.Authenticate(authUrl) + RunE: func(cmd *cobra.Command, args []string) error { + return Service.Authenticate() }, } diff --git a/cmd/init.go b/cmd/init.go index fa14916..7c43705 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -12,6 +12,6 @@ var initCmd = &cobra.Command{ Use: "init", Short: "First time DCE cli setup. Creates config file at ~/.dce.yaml", Run: func(cmd *cobra.Command, args []string) { - service.InitializeDCE(cfgFile) + Service.InitializeDCE() }, } diff --git a/cmd/leases.go b/cmd/leases.go index e52ebc1..874a546 100644 --- a/cmd/leases.go +++ b/cmd/leases.go @@ -68,7 +68,7 @@ var leasesDescribeCmd = &cobra.Command{ Short: "describe a lease", Args: cobra.ExactValidArgs(1), Run: func(cmd *cobra.Command, args []string) { - service.GetLease(args[0]) + Service.GetLease(args[0]) }, } @@ -77,7 +77,7 @@ var leasesListCmd = &cobra.Command{ Short: "List leases using various query filters.", Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { - service.ListLeases(acctID, principalID, nextAcctID, nextPrincipalID, leaseStatus, pagLimit) + Service.ListLeases(acctID, principalID, nextAcctID, nextPrincipalID, leaseStatus, pagLimit) }, } @@ -86,7 +86,7 @@ var leasesCreateCmd = &cobra.Command{ Short: "Create a lease.", Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { - service.CreateLease(principalID, budgetAmount, budgetCurrency, email) + Service.CreateLease(principalID, budgetAmount, budgetCurrency, email) }, } @@ -95,7 +95,7 @@ var leasesEndCmd = &cobra.Command{ Short: "Cause a lease to immediately expire", Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { - service.EndLease(accountID, principalID) + Service.EndLease(accountID, principalID) }, } @@ -104,6 +104,6 @@ var leasesLoginCmd = &cobra.Command{ Short: "Login to a leased DCE account. (Sets AWS CLI credentials if used with no flags)", Args: cobra.ExactArgs(1), Run: func(cmd *cobra.Command, args []string) { - service.LoginToLease(args[0], loginProfile, loginOpenBrowser, loginPrintCreds) + Service.LoginToLease(args[0], loginProfile, loginOpenBrowser, loginPrintCreds) }, } diff --git a/cmd/root.go b/cmd/root.go index d16e00b..9710e33 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -16,7 +16,9 @@ limitations under the License. package cmd import ( + "errors" "fmt" + "github.com/mitchellh/go-homedir" "os" "path/filepath" @@ -27,18 +29,32 @@ import ( svc "github.com/Optum/dce-cli/pkg/service" "github.com/sirupsen/logrus" "github.com/spf13/cobra" - "github.com/spf13/viper" ) var cfgFile string -var config *configs.Root = &configs.Root{} -var service *svc.ServiceContainer -var util *utl.UtilContainer -var observation *observ.ObservationContainer +var Config = &configs.Root{} +var Service *svc.ServiceContainer +var Util *utl.UtilContainer +var Observation *observ.ObservationContainer +// Expose logger as global for ease of use +var log observ.Logger +var Log observ.Logger func init() { - cobra.OnInitialize(initObservation, initConfig, initUtil, initService) - RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.dce.yaml)") + homeDir, err := homedir.Dir() + if err != nil { + log.Fatalf("error: %v", err) + } + + // Global Flags + // --------------- + // --config flag, to specify path to dce.yml config + // default to ~/.dce.yml + RootCmd.PersistentFlags().StringVar( + &cfgFile, "config", + filepath.Join(homeDir, constants.DefaultConfigFileName), + "config file", + ) } // RootCmd represents the base command when called without any subcommands @@ -51,14 +67,41 @@ var RootCmd = &cobra.Command{ - Admins to provision DCE to a master account and administer said account - Users to lease accounts and execute commands against them`, + PersistentPreRunE: preRun, +} + +func preRun(cmd *cobra.Command, args []string) error { + err := onInit(cmd, args) + if err != nil { + return err + } + + // Check if the user has valid creds, + // otherwise require authentication + creds := Util.AWSSession.Config.Credentials + _, _ = creds.Get() + hasValidCreds := !creds.IsExpired() + isAuthCommand := cmd.Name() == authCmd.Name() + isInitCommand := cmd.Name() == initCmd.Name() + if !hasValidCreds && !isAuthCommand && !isInitCommand { + log.Print("No valid DCE credentials found") + err := Service.Authenticate() + if err != nil { + return err + } + } + + return nil } // Execute adds all child commands to the root command and sets flags appropriately. func Execute() { if err := RootCmd.Execute(); err != nil { - fmt.Println(err) os.Exit(1) } + // Print an extra newline when we're done, + // so users terminal prompt shows up on a new line + fmt.Println("") } type FmtOutputFormatter struct { @@ -66,13 +109,41 @@ type FmtOutputFormatter struct { func (f *FmtOutputFormatter) Format(entry *logrus.Entry) ([]byte, error) { var serialized []byte - var err error serialized = []byte(entry.Message) - if err != nil { - return nil, fmt.Errorf("Failed to marshal fields to JSON, %v", err) + return serialized, nil +} + +func onInit(cmd *cobra.Command, args []string) error { + // Configure observation / logging + initObservation() + // Expose global `log` object for ease of use + log = Observation.Logger + Log = log + + fsUtil := &utl.FileSystemUtil{Config: Config, ConfigFile: cfgFile} + + // Initialize config + // If config file does not exist, + // run the `dce init` command + if !fsUtil.IsExistingFile(cfgFile) { + if cmd.Name() != initCmd.Name() { + return errors.New("Config file not found. Please type 'dce init' to generate one.") + } + } else { + // Load config from dce.yaml + err := fsUtil.ReadInConfig() + if err != nil { + return fmt.Errorf("Failed to parse dce.yml: %s", err) + } } - fmt.Println(string(serialized)) - return nil, nil + + // initialize utilities and interfaces to external things + Util = utl.New(Config, cfgFile, Observation) + + // initialize business logic services + Service = svc.New(Config, Observation, Util) + + return nil } // initialize anything related to logging, metrics, or tracing @@ -107,48 +178,5 @@ func initObservation() { logrusInstance.SetFormatter(&logrus.TextFormatter{}) } - observation = observ.New(logrusInstance) -} - -// Utils we need before they are normally instantiated -var log observ.Logger -var fsUtil utl.FileSystemer - -// initialize config from file or tell user to run 'dce init' if none exists -func initConfig() { - - tempUtil := utl.New(config, observation) - fsUtil = tempUtil.FileSystemer - log = observation.Logger - - var configFileUsed string - if cfgFile != "" { - configFileUsed = cfgFile - viper.SetConfigFile(configFileUsed) - } else { - home := fsUtil.GetHomeDir() - configFileUsed = filepath.Join(home, constants.DefaultConfigFileName) - viper.SetConfigFile(configFileUsed) - } - - if !fsUtil.IsExistingFile(configFileUsed) { - if len(os.Args) < 2 || os.Args[1] != initCmd.Name() { - log.Endln("Config file not found. Please type 'dce init' to generate one.") - } - } else { - if err := viper.ReadInConfig(); err == nil { - viper.BindEnv("githubtoken", "GITHUB_TOKEN") - viper.Unmarshal(config) - } - } -} - -// initialize utilities and interfaces to external things -func initUtil() { - util = utl.New(config, observation) -} - -// initialize business logic -func initService() { - service = svc.New(config, observation, util) -} + Observation = observ.New(logrusInstance) +} \ No newline at end of file diff --git a/cmd/system.go b/cmd/system.go index 4301745..7eff7c3 100644 --- a/cmd/system.go +++ b/cmd/system.go @@ -36,6 +36,6 @@ var systemDeployCmd = &cobra.Command{ Use: "deploy", Short: "Deploy DCE to a new master account", Run: func(cmd *cobra.Command, args []string) { - service.Deploy(deployLocalPath, &deployOverrides) + Service.Deploy(deployLocalPath, &deployOverrides) }, } diff --git a/cmd/usage.go b/cmd/usage.go index 0a9f874..00d5a26 100644 --- a/cmd/usage.go +++ b/cmd/usage.go @@ -20,6 +20,6 @@ var usageCmd = &cobra.Command{ Short: "View lease budget information", Args: cobra.NoArgs, Run: func(cmd *cobra.Command, args []string) { - service.GetUsage(startDate, endDate) + Service.GetUsage(startDate, endDate) }, } diff --git a/configs/config.go b/configs/config.go index 7b04a04..39a4e8a 100644 --- a/configs/config.go +++ b/configs/config.go @@ -2,17 +2,17 @@ package configs // Root contains config type Root struct { - System struct { - Auth struct { - LoginURL *string - } - } - API struct { - Host *string - BasePath *string - } + API API Region *string - GithubToken *string + GithubToken *string `yaml:"githubToken,omitempty"` +} + +type API struct { + Host *string + BasePath *string + // Token for authenticating against the API + // token is base64 encoded JSON, containing an STS token. + Token *string `yaml:"token,omitempty"` } var Regions = []string{"us-east-1", "us-east-2", "us-west-1", "us-west-2"} diff --git a/go.mod b/go.mod index 2b05ec4..1a4d8e9 100644 --- a/go.mod +++ b/go.mod @@ -19,15 +19,18 @@ replace github.com/ugorji/go v1.1.4 => github.com/ugorji/go v0.0.0-2018120416352 require ( github.com/aws/aws-sdk-go v1.25.16 - github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e + github.com/coreos/bbolt v1.3.2 // indirect + github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e // indirect github.com/dsnet/compress v0.0.1 // indirect + github.com/frankban/quicktest v1.7.0 // indirect github.com/go-openapi/errors v0.19.2 github.com/go-openapi/runtime v0.19.7 - github.com/go-openapi/spec v0.19.3 github.com/go-openapi/strfmt v0.19.3 github.com/go-openapi/swag v0.19.5 github.com/go-openapi/validate v0.19.3 - github.com/golang/example v0.0.0-20170904185048-46695d81d1fa // indirect + github.com/gogo/protobuf v1.2.1 // indirect + github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef // indirect + github.com/grpc-ecosystem/grpc-gateway v1.9.0 // indirect github.com/hashicorp/terraform v0.12.10 github.com/manifoldco/promptui v0.3.2 github.com/mholt/archiver v3.1.1+incompatible @@ -36,15 +39,22 @@ require ( github.com/nwaples/rardecode v1.0.0 // indirect github.com/pierrec/lz4 v2.3.0+incompatible // indirect github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4 + github.com/prometheus/client_golang v0.9.3 // indirect github.com/shurcooL/githubv4 v0.0.0-20191006152017-6d1ea27df521 github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f // indirect github.com/sirupsen/logrus v1.2.0 github.com/spf13/cobra v0.0.5 - github.com/spf13/viper v1.4.0 github.com/stretchr/testify v1.4.0 + github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 // indirect github.com/ugorji/go v1.1.7 // indirect github.com/vektra/mockery v0.0.0-20181123154057-e78b021dcbb5 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect + github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect + go.etcd.io/bbolt v1.3.2 // indirect + go.uber.org/atomic v1.4.0 // indirect + go.uber.org/thriftrw v1.20.2 + go.uber.org/zap v1.10.0 // indirect golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45 + gopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20191105091915-95d230a53780 // indirect gopkg.in/yaml.v2 v2.2.4 ) diff --git a/go.sum b/go.sum index 66a22f3..25ea753 100644 --- a/go.sum +++ b/go.sum @@ -25,13 +25,16 @@ github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/Unknwon/com v0.0.0-20151008135407-28b053d5a292 h1:tuQ7w+my8a8mkwN7x2TSd7OzTjkZ7rAeSyH4xncuAMI= github.com/Unknwon/com v0.0.0-20151008135407-28b053d5a292/go.mod h1:KYCjqMOeHpNuTOiFQU6WEcTG7poCJrUs0YgyHNtn1no= +github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af h1:DBNMBMuMiWYu0b+8KMJuWmfCkcxl09JwdlqwDZZ6U14= github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af/go.mod h1:5Jv4cbFiHJMsVxt52+i0Ha45fjshj6wxYr1r19tB9bw= github.com/agext/levenshtein v1.2.1/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/agext/levenshtein v1.2.2 h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXvaqE= github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/agl/ed25519 v0.0.0-20150830182803-278e1ec8e8a6/go.mod h1:WPjqKcmVOxf0XSf3YxCJs6N6AOSrOx3obionmG7T0y0= +github.com/alecthomas/gometalinter v2.0.11+incompatible h1:ENdXMllZNSVDTJUUVIzBW9CSEpntTrQa76iRsEFLX/M= github.com/alecthomas/gometalinter v2.0.11+incompatible/go.mod h1:qfIpQGGz3d+NmgyPBqv+LSh50emm1pt72EtcX2vKYQk= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf h1:qet1QNfXsQxTZqLG4oE62mJzwPIB8+Tee4RNCL9ulrY= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190329064014-6e358769c32a h1:APorzFpCcv6wtD5vmRWYqNm4N55kbepL7c7kTq9XI6A= github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190329064014-6e358769c32a/go.mod h1:T9M45xf79ahXVelWoOBmH0y4aC1t5kXO5BxwyakgIGA= @@ -39,17 +42,21 @@ github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190103054945-8205d1f41e70 h1:FrF4ux github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20190103054945-8205d1f41e70/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= github.com/aliyun/aliyun-tablestore-go-sdk v4.1.2+incompatible h1:ABQ7FF+IxSFHDMOTtjCfmMDMHiCq6EsAoCV/9sFinaM= github.com/aliyun/aliyun-tablestore-go-sdk v4.1.2+incompatible/go.mod h1:LDQHRZylxvcg8H7wBIDfvO5g/cy4/sz1iucBlc2l3Jw= +github.com/antchfx/xpath v0.0.0-20190129040759-c8489ed3251e h1:ptBAamGVd6CfRsUtyHD+goy2JGhv1QC32v3gqM8mYAM= github.com/antchfx/xpath v0.0.0-20190129040759-c8489ed3251e/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk= +github.com/antchfx/xquery v0.0.0-20180515051857-ad5b8c7a47b0 h1:JaCC8jz0zdMLk2m+qCCVLLLM/PL93p84w4pK3aJWj60= github.com/antchfx/xquery v0.0.0-20180515051857-ad5b8c7a47b0/go.mod h1:LzD22aAzDP8/dyiCKFp31He4m2GPjl0AFyzDtZzUu9M= github.com/apparentlymart/go-cidr v1.0.1 h1:NmIwLZ/KdsjIUlhf+/Np40atNXm/+lZ5txfTJ/SpF+U= github.com/apparentlymart/go-cidr v1.0.1/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc= github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= +github.com/apparentlymart/go-dump v0.0.0-20190214190832-042adf3cf4a0 h1:MzVXffFUye+ZcSR6opIgz9Co7WcDx6ZcY+RjfFHoA0I= github.com/apparentlymart/go-dump v0.0.0-20190214190832-042adf3cf4a0/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM= github.com/apparentlymart/go-textseg v1.0.0 h1:rRmlIsPEEhUTIKQb7T++Nz/A5Q6C9IuX2wFoYVvnCs0= github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk= github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2 h1:7Ip0wMmLHLRJdrloDxZfhMm0xrLXZS8+COSu2bXmEQs= github.com/armon/circbuf v0.0.0-20190214190532-5111143e8da2/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 h1:8GUt8eRujhVEGZFFEjBj46YV4rDjvGrNxb0KMWYkL2I= 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/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= @@ -60,11 +67,12 @@ github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:l github.com/aws/aws-sdk-go v1.15.78/go.mod h1:E3/ieXAlvM0XWO57iftYVDLLvQ824smPP3ATZkfNZeM= github.com/aws/aws-sdk-go v1.16.36/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.22.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go v1.25.3/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.25.16 h1:k7Fy6T/uNuLX6zuayU/TJoP7yMgGcJSkZpF7QVjwYpA= github.com/aws/aws-sdk-go v1.25.16/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f h1:ZNv7On9kyUzm7fvRZumSyy/IUiSC7AzL0I1jKKtwooA= github.com/baiyubin/aliyun-sts-go-sdk v0.0.0-20180326062324-cfa1a18b161f/go.mod h1:AuiFmCCPBSrqvVMvuqFuk0qogytodnVFVSN5CeJB8Gc= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0 h1:HWo1m869IqiPhD389kmkxeTalrjNbbJTC8LXupb+sl0= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= @@ -84,8 +92,10 @@ github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5O github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1 h1:q763qf9huN11kDQavWsoZXJNW3xEE4JJyHa5Q25/sd8= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/coreos/bbolt v1.3.0/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= +github.com/coreos/bbolt v1.3.2 h1:wZwiHHUieZCquLkDL0B8UhzreNWsPHooDAG3q34zk0s= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= github.com/coreos/etcd v3.3.10+incompatible h1:jFneRYjIvLMLhDLCzuTuU4rSJUjRplcJQ7pD7MnhC04= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= @@ -93,7 +103,9 @@ github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8Nz github.com/coreos/go-semver v0.2.0 h1:3Jm3tLmsgAYcjC+4Up7hJrFBPr+n7rAqYeSw/SZazuY= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/coreos/go-systemd v0.0.0-20181012123002-c6f51f82210d/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e h1:Wf6HqHfScWJN9/ZjdUKyjop4mf3Qdd+1TvvltAvM3m8= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f h1:lBNOc5arjvs8E5mO2tbpBpLoyyu8B6e44T7hJy6potg= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= @@ -105,6 +117,7 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= github.com/dimchansky/utfbom v1.0.0 h1:fGC2kkf4qOoKqZ4q7iIh+Vef4ubC1c38UDsEyZynZPc= github.com/dimchansky/utfbom v1.0.0/go.mod h1:rO41eb7gLfo8SF1jd9F8HplJm1Fewwi4mQvIirEdv+8= +github.com/dnaeon/go-vcr v0.0.0-20180920040454-5637cf3d8a31 h1:Dzuw9GtbmllUqEcoHfScT9YpKFUssSiZ5PgZkIGf/YQ= github.com/dnaeon/go-vcr v0.0.0-20180920040454-5637cf3d8a31/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= github.com/docker/go-units v0.3.3/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= @@ -113,11 +126,15 @@ github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5Jflh github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= github.com/dylanmei/iso8601 v0.1.0 h1:812NGQDBcqquTfH5Yeo7lwR0nzx/cKdsmf3qMjPURUI= github.com/dylanmei/iso8601 v0.1.0/go.mod h1:w9KhXSgIyROl1DefbMYIE7UVSIvELTbMrCfx+QkYnoQ= +github.com/dylanmei/winrmtest v0.0.0-20190225150635-99b7fe2fddf1 h1:r1oACdS2XYiAWcfF8BJXkoU8l1J71KehGR+d99yWEDA= github.com/dylanmei/winrmtest v0.0.0-20190225150635-99b7fe2fddf1/go.mod h1:lcy9/2gH1jn/VCLouHA6tOEwLoNVd4GW6zhuKLmHC2Y= github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/frankban/quicktest v1.7.0 h1:dwIEggvZoS4J8ft6vgpnP000Z8DfcN/hMD3Cg+1Li0E= +github.com/frankban/quicktest v1.7.0/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o= 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 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/globalsign/mgo v0.0.0-20180905125535-1ca0a4f7cbcb/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= @@ -177,19 +194,22 @@ github.com/go-openapi/validate v0.19.3 h1:PAH/2DylwWcIU1s0Y7k3yNmeAgWOcKrNE2Q7Ww github.com/go-openapi/validate v0.19.3/go.mod h1:90Vh6jjkTn+OT1Eefm0ZixWNFjhtOH7vS9k0lo6zwJo= github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/golang/example v0.0.0-20170904185048-46695d81d1fa h1:iqCQC2Z53KkwGgTN9szyL4q0OQHmuNjeoNnMT6lk66k= -github.com/golang/example v0.0.0-20170904185048-46695d81d1fa/go.mod h1:tO/5UvQ/uKigUjQBPqzstj6uxd3fUIjddi19DxGJeWg= +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/groupcache v0.0.0-20180513044358-24b0969c4cb7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef h1:veQD95Isof8w9/WXiA+pa3tz3fJXkt5B7QaRBrM62gk= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/lint v0.0.0-20181026193005-c67002cb31c3 h1:I4BOK3PBMjhWfQM2zPJKK7lOBGsrsvOB7kBELP33hiE= github.com/golang/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:tluoj9z5200jBnyusfRPU2LqT6J+DAorxEvtC7LHB+E= 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 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s= github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -199,6 +219,7 @@ github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db h1:woRePGFeVFfLKN/pOkfl+p/TAqKOfFu+7KPlMVpok/w= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0 h1:0udJVsspx3VBr5FwtLhQQtuAsVc79tTq0ocGIPAU6qo= 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 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= @@ -207,9 +228,11 @@ github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= 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/shlex v0.0.0-20181106134648-c34317bd91bf h1:7+FW5aGwISbqUtkfmIpZJGRgNFg2ioYPvFaUxdqpDsg= github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE= github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY= @@ -221,17 +244,21 @@ github.com/gophercloud/gophercloud v0.0.0-20190208042652-bc37892e1968 h1:Pu+HW4k github.com/gophercloud/gophercloud v0.0.0-20190208042652-bc37892e1968/go.mod h1:3WdhXV3rUYy9p6AUW8d94kr+HS62Y4VL9mBnFxsD8q4= github.com/gophercloud/utils v0.0.0-20190128072930-fbb6ab446f01 h1:OgCNGSnEalfkRpn//WGJHhpo7fkP+LhTpvEITZ7CkK4= github.com/gophercloud/utils v0.0.0-20190128072930-fbb6ab446f01/go.mod h1:wjDF8z83zTeg5eMLml5EBSlAhbF7G8DobyI1YsMuyzw= +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/gordonklaus/ineffassign v0.0.0-20180909121442-1003c8bd00dc h1:cJlkeAx1QYgO5N80aF5xRGstVsRQwgLR7uA2FnP1ZjY= github.com/gordonklaus/ineffassign v0.0.0-20180909121442-1003c8bd00dc/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU= +github.com/gorilla/websocket v1.4.0 h1:WDFjx/TMzVgy9VdMMQi2K2Emtwi2QcUQsztZ/zLaH/Q= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 h1:Iju5GlWwrvL6UBg4zJJt3btmonfrMlCDdsejg4CZE7c= 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 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/grpc-ecosystem/grpc-gateway v1.9.0 h1:bM6ZAFZmc/wPFaRDi0d5L7hGEZEx/2u+Tmr2evNHDiI= github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= github.com/hashicorp/aws-sdk-go-base v0.3.0 h1:CPWKWCuOwpIFNsy8FUI9IT2QI7mGwgVPc4hrXW9I4L4= github.com/hashicorp/aws-sdk-go-base v0.3.0/go.mod h1:ZIWACGGi0N7a4DZbf15yuE1JQORmWLtBcVM6F5SXNFU= -github.com/hashicorp/aws-sdk-go-base v0.4.0 h1:zH9hNUdsS+2G0zJaU85ul8D59BGnZBaKM+KMNPAHGwk= -github.com/hashicorp/aws-sdk-go-base v0.4.0/go.mod h1:eRhlz3c4nhqxFZJAahJEFL7gh6Jyj5rQmQc7F9eHFyQ= github.com/hashicorp/consul v0.0.0-20171026175957-610f3c86a089 h1:1eDpXAxTh0iPv+1kc9/gfSI2pxRERDsTk/lNGolwHn8= github.com/hashicorp/consul v0.0.0-20171026175957-610f3c86a089/go.mod h1:mFrjN1mfidgJfYP1xrJCF+AfRhr6Eaqhb2+sfyn/OOI= github.com/hashicorp/errwrap v0.0.0-20180715044906-d6c0cd880357/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -242,14 +269,14 @@ github.com/hashicorp/go-azure-helpers v0.0.0-20190129193224-166dfd221bb2/go.mod github.com/hashicorp/go-checkpoint v0.5.0/go.mod h1:7nfLNL10NsxqO4iWuW6tWW0HjZuDrwkBuEQsVcpCOgg= github.com/hashicorp/go-cleanhttp v0.5.0 h1:wvCrVc9TjDls6+YGAF2hAifE1E5U1+b4tH6KdvN3Gig= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-cleanhttp v0.5.1 h1:dH3aiDG9Jvb5r5+bYHsikaOUIpcM0xvgMXVoDkXMzJM= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-getter v1.4.0 h1:ENHNi8494porjD0ZhIrjlAHnveSFhY7hvOJrV/fsKkw= github.com/hashicorp/go-getter v1.4.0/go.mod h1:7qxyCd8rBfcShwsvxgIguu4KbS3l8bUCwg2Umn7RjeY= github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= github.com/hashicorp/go-hclog v0.0.0-20181001195459-61d530d6c27f h1:Yv9YzBlAETjy6AOX9eLBZ3nshNVRREgerT/3nvxlGho= github.com/hashicorp/go-hclog v0.0.0-20181001195459-61d530d6c27f/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= +github.com/hashicorp/go-immutable-radix v0.0.0-20180129170900-7f3cd4390caa h1:0nA8i+6Rwqaq9xlpmVxxTwk6rxiEhX+E6Wh4vPNHiS8= github.com/hashicorp/go-immutable-radix v0.0.0-20180129170900-7f3cd4390caa/go.mod h1:6ij3Z20p+OhOkCSrA0gImAWoHYQRGbnlcuk6XYTiaRw= +github.com/hashicorp/go-msgpack v0.5.4 h1:SFT72YqIkOcLdWJUYcriVX7hbrZpwc/f7h8aW2NUqrA= github.com/hashicorp/go-msgpack v0.5.4/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= github.com/hashicorp/go-multierror v0.0.0-20180717150148-3d5d8f294aa0/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= github.com/hashicorp/go-multierror v1.0.0 h1:iVjPR7a6H0tWELX5NxNe7bYopibicUzc7uPribsnS6o= @@ -264,20 +291,15 @@ github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhE github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= github.com/hashicorp/go-slug v0.3.0 h1:L0c+AvH/J64iMNF4VqRaRku2DMTEuHioPVS7kMjWIU8= github.com/hashicorp/go-slug v0.3.0/go.mod h1:I5tq5Lv0E2xcNXNkmx7BSfzi1PsJ2cNjs3cC3LwyhK8= -github.com/hashicorp/go-slug v0.4.0 h1:YSz3afoEZZJVVB46NITf0+opd2cHpaYJ1XSojOyP0x8= -github.com/hashicorp/go-slug v0.4.0/go.mod h1:I5tq5Lv0E2xcNXNkmx7BSfzi1PsJ2cNjs3cC3LwyhK8= +github.com/hashicorp/go-sockaddr v0.0.0-20180320115054-6d291a969b86 h1:7YOlAIO2YWnJZkQp7B5eFykaIY7C9JndqAFQyVV5BhM= github.com/hashicorp/go-sockaddr v0.0.0-20180320115054-6d291a969b86/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= github.com/hashicorp/go-tfe v0.3.23 h1:kd9hlFQvGubNF/CpF7T5AP/xU8uLUq8ANbI5xRDVSms= github.com/hashicorp/go-tfe v0.3.23/go.mod h1:SuPHR+OcxvzBZNye7nGPfwZTEyd3rWPfLVbCgyZPezM= -github.com/hashicorp/go-tfe v0.3.25 h1:4rPk/9rSYuRoujKk5FsxSvtC/AjJCQphLS/57yr6wUM= -github.com/hashicorp/go-tfe v0.3.25/go.mod h1:IJQ30WzRajD/W0Z8SY4lhuoOX8h5saTe95t80z8hRsk= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.1 h1:fv1ep09latC32wFoVwnqcnKJGnMSdBanPczbHAYm1BE= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.1.0 h1:bPIoEKD27tNdebFGGxxYwcL4nepeY4j1QP23PFRGzg0= github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E= -github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= @@ -292,17 +314,14 @@ github.com/hashicorp/hil v0.0.0-20190212112733-ab17b08d6590 h1:2yzhWGdgQUWZUCNK+ github.com/hashicorp/hil v0.0.0-20190212112733-ab17b08d6590/go.mod h1:n2TSygSNwsLJ76m8qFXTSc7beTb+auJxYdqrnoqwZWE= github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/memberlist v0.1.0 h1:qSsCiC0WYD39lbSitKNt40e30uorm2Ss/d4JGU1hzH8= github.com/hashicorp/memberlist v0.1.0/go.mod h1:ncdBp14cuox2iFOq3kDiquKU6fqsTBc3W6JvZwjxxsE= github.com/hashicorp/serf v0.0.0-20160124182025-e4ec8cc423bb h1:ZbgmOQt8DOg796figP87/EFCVx2v2h9yRvwHF/zceX4= github.com/hashicorp/serf v0.0.0-20160124182025-e4ec8cc423bb/go.mod h1:h/Ru6tmZazX7WO/GDmwdpS975F019L4t5ng5IgwbNrE= github.com/hashicorp/terraform v0.12.10 h1:+wKsR1FRWrHcbIdbzD9v99YDR+PwTOlzgW+NigGWeLA= github.com/hashicorp/terraform v0.12.10/go.mod h1:IwGTrz19rMZSVaU7ox2Wbmd4JrJEe5z6Q8EO98r7b6E= -github.com/hashicorp/terraform v0.12.12 h1:c71+dDT8TbiVDCaQPvkEhdvTqyrP55s7POa6D5aUBSM= -github.com/hashicorp/terraform v0.12.12/go.mod h1:BBG6fbrnZ9UVmdrm9VQ2cJ/R9G3IQ1eQ7Ru3nC0yt3s= github.com/hashicorp/terraform-config-inspect v0.0.0-20190821133035-82a99dc22ef4 h1:fTkL0YwjohGyN7AqsDhz6bwcGBpT+xBqi3Qhpw58Juw= github.com/hashicorp/terraform-config-inspect v0.0.0-20190821133035-82a99dc22ef4/go.mod h1:JDmizlhaP5P0rYTTZB0reDMefAiJyfWPEtugV4in1oI= -github.com/hashicorp/terraform-svchost v0.0.0-20191011084731-65d371908596 h1:hjyO2JsNZUKT1ym+FAdlBEkGPevazYsmVgIMw7dVELg= -github.com/hashicorp/terraform-svchost v0.0.0-20191011084731-65d371908596/go.mod h1:kNDNcF7sN4DocDLBkQYz73HGKwN1ANB1blq4lIYLYvg= github.com/hashicorp/vault v0.10.4/go.mod h1:KfSyffbKxoVyspOdlaGVjIuwLobi07qD1bAbosPMpP0= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= @@ -313,12 +332,14 @@ github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJS github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af h1:pmfjZENx5imkbgOkpRUYLnmbU7UEFbjtDA2hxJ1ichM= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/joyent/triton-go v0.0.0-20180313100802-d8f9c0314926 h1:kie3qOosvRKqwij2HGzXWffwpXvcqfPPXRUw8I4F/mg= github.com/joyent/triton-go v0.0.0-20180313100802-d8f9c0314926/go.mod h1:U+RSyWxWd04xTqnuOQxnai7XGS2PrPY2cfGoDKtMHjA= github.com/json-iterator/go v1.1.5 h1:gL2yXlmiIo4+t+y32d4WGwOjKGYcGOuyrg46vadswDE= github.com/json-iterator/go v1.1.5/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.2.1+incompatible h1:fSuqC+Gmlu6l/ZYAoZzx2pyucC8Xza35fpRVWLVmUEE= github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU= github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU= @@ -331,6 +352,7 @@ github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvW github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= 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= @@ -340,6 +362,7 @@ github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= 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/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/lib/pq v1.0.0 h1:X5PMW56eZitiTeO7tKzZxFCSpbFZJtkMMooicw2us9A= github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= @@ -371,9 +394,11 @@ github.com/mattn/go-isatty v0.0.5 h1:tHXDdz1cpzGaovsTB+TVB8q90WEokoVmfMqoVcrLUgw github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-shellwords v1.0.4/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= +github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/mholt/archiver v3.1.1+incompatible h1:1dCVxuqs0dJseYEhi5pl7MYPH9zDa1wBi7mF09cbNkU= github.com/mholt/archiver v3.1.1+incompatible/go.mod h1:Dh2dOXnSdiLxRiPoVfIr/fI1TwETms9B8CTWfeh7ROU= +github.com/miekg/dns v1.0.8 h1:Zi8HNpze3NeRWH1PQV6O71YcvJRQ6j0lORO6DAEmAAI= github.com/miekg/dns v1.0.8/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0 h1:iGBIsUe3+HZ/AD/Vd7DErOt5sU9fa8Uj7A2s1aggv1Y= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= @@ -418,6 +443,7 @@ github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+W github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/packer-community/winrmcp v0.0.0-20180102160824-81144009af58 h1:m3CEgv3ah1Rhy82L+c0QG/U3VyY1UsvsIdkh0/rU97Y= github.com/packer-community/winrmcp v0.0.0-20180102160824-81144009af58/go.mod h1:f6Izs6JvFTdnRbziASagjZ2vmf55NSIkC/weStxCHqk= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c h1:Lgl0gzECD8GnQ5QCWA8o6BtfL6mDH5rQgM4/fX3avOs= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc= @@ -435,15 +461,19 @@ github.com/posener/complete v1.2.1 h1:LrvDIY//XNo65Lq84G/akBuMGlawHvGBABv8f/ZN6D github.com/posener/complete v1.2.1/go.mod h1:6gapUrK/U1TAN7ciCoNRIdVC5sbdBTUh1DKN0g6uH7E= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= +github.com/prometheus/client_golang v0.9.3 h1:9iH4JKXLzFbOAdtqv/a+j8aewx2Y8lAjAydhbaScPF8= 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-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 h1:S/YWwWx/RA8rT8tKFRuGUZhuA90OyIBpPCXkcbwU8DE= 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.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.0 h1:7etb9YClo3a6HjLzfl6rIQaU+FDfi0VSX39io3aQ+DM= 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-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084 h1:sofwID9zm4tzrgykg80hfFph1mryUeLRsUfoocVVmRY= 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= @@ -451,7 +481,9 @@ github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNue github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= github.com/shurcooL/githubv4 v0.0.0-20191006152017-6d1ea27df521 h1:ARaYJO1zp2afVv0s28fq7uxgee4WLop35FWrOoSZyak= github.com/shurcooL/githubv4 v0.0.0-20191006152017-6d1ea27df521/go.mod h1:hAF0iLZy4td2EX+/8Tw+4nodhlMrwN3HupfaXj3zkGo= @@ -459,8 +491,11 @@ github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f h1:tygelZueB1EtXk github.com/shurcooL/graphql v0.0.0-20181231061246-d48a9a75455f/go.mod h1:AuYgA5Kyo4c7HfUmvRGs/6rGlMMV/6B1bVnB9JxJEEg= github.com/sirupsen/logrus v1.2.0 h1:juTguoYk5qI21pwyTXY3B3Y5cOTH3ZUyZCg1v/mihuo= 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 v0.0.0-20180222194500-ef6db91d284a h1:JSvGDIbmil4Ui/dDdFBExb7/cmkNjyX5F97oglmvCDo= github.com/smartystreets/goconvey v0.0.0-20180222194500-ef6db91d284a/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= +github.com/soheilhy/cmux v0.1.4 h1:0HKaf1o97UwFjHH9o5XsHUOF+tqmdA7KEzXLpiyaw0E= 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/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= @@ -468,9 +503,6 @@ github.com/spf13/afero v1.2.1 h1:qgMbHoJbPbw579P+1zVY+6n4nIFuIchaIjzZ/I/Yq8M= github.com/spf13/afero v1.2.1/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= 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 v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8= -github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v0.0.4/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk= @@ -480,8 +512,6 @@ 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.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= -github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU= -github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -496,17 +526,14 @@ github.com/svanharmelen/jsonapi v0.0.0-20180618144545-0c0828c3f16d h1:Z4EH+5Effv github.com/svanharmelen/jsonapi v0.0.0-20180618144545-0c0828c3f16d/go.mod h1:BSTlc8jOjh0niykqEGVXOLXdi9o0r0kR8tCYiMvjFgw= github.com/terraform-providers/terraform-provider-openstack v1.15.0 h1:adpjqej+F8BAX9dHmuPF47sUIkgifeqBu6p7iCsyj0Y= github.com/terraform-providers/terraform-provider-openstack v1.15.0/go.mod h1:2aQ6n/BtChAl1y2S60vebhyJyZXBsuAI5G4+lHrT1Ew= +github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tmc/grpc-websocket-proxy v0.0.0-20171017195756-830351dc03c6/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 h1:LnC5Kc/wtumK+WB441p7ynQJzVuNRJiqddSIE3IlSEQ= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= +github.com/tsenart/deadcode v0.0.0-20160724212837-210d2dc333e9 h1:vY5WqiEon0ZSTGM3ayVVi+twaHKHDFUVloaQ/wug9/c= github.com/tsenart/deadcode v0.0.0-20160724212837-210d2dc333e9/go.mod h1:q+QjxYvZ+fpjMXqs+XEriussHjSYqeXVnAdSV1tkMYk= github.com/ugorji/go v0.0.0-20180813092308-00b869d2f4a5/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= -github.com/ugorji/go v0.0.0-20181204163529-d75b2dcb6bc8 h1:Kcv6ck5PWsaDifMwDDgexKfbmP1k63r3tcVppPa+FyM= -github.com/ugorji/go v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= -github.com/ugorji/go v1.1.1 h1:gmervu+jDMvXTbcHQ0pd2wee85nEoE0BsVyEuzkfK8w= -github.com/ugorji/go v1.1.1/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ= -github.com/ugorji/go v1.1.4 h1:j4s+tAvLfL3bZyefP2SEWmhBzmuIlH/eqNuPdFPgngw= -github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8 h1:3SVOIvH7Ae1KRYyQWRjXWJEA9sS/c/pjvH++55Gr648= @@ -527,6 +554,7 @@ github.com/xanzy/ssh-agent v0.2.1/go.mod h1:mLlQY/MoOhWBj+gOGMQkOeiEvkx+8pJSI+0B github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= github.com/xiang90/probing v0.0.0-20160813154853-07dd2e8dfe18/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 h1:eY9dn8+vbi4tKz5Qo6v2eYzo7kUS51QINcR5jNpbZS8= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xlab/treeprint v0.0.0-20161029104018-1d6e34225557 h1:Jpn2j6wHkC9wJv5iMfJhKqrZJx3TahFx+7sbZ7zQdxs= github.com/xlab/treeprint v0.0.0-20161029104018-1d6e34225557/go.mod h1:ce1O1j6UtZfjr22oyGxGLbauSBp2YVXpARAosm7dHBg= @@ -536,6 +564,7 @@ github.com/zclconf/go-cty v1.1.0 h1:uJwc9HiBOCpoKIObTQaLR+tsEXx1HBHnOsOOpcdhZgw= github.com/zclconf/go-cty v1.1.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s= github.com/zclconf/go-cty-yaml v1.0.1 h1:up11wlgAaDvlAGENcFDnZgkn0qUJurso7k6EpURKNF8= github.com/zclconf/go-cty-yaml v1.0.1/go.mod h1:IP3Ylp0wQpYm50IHK8OZWKMu6sPJIUgKa8XhiVHura0= +go.etcd.io/bbolt v1.3.2 h1:Z/90sZLPOeCy2PwprqkFa25PdkusRzaj9P8zm/KNyvk= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.mongodb.org/mongo-driver v1.0.3/go.mod h1:u7ryQJ+DOzQmeO7zB6MHyr8jkEQvC8vH7qLUO4lqsUM= go.mongodb.org/mongo-driver v1.1.1 h1:Sq1fR+0c58RME5EoqKdjkiQAmPjmfHlZOoRI6fTUOcs= @@ -544,9 +573,14 @@ go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0 h1:C9hSCOW830chIVkdja34wa6Ky+IzWllkUinR+BtRZd4= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0 h1:cxzIVoETapQEqDhQu3QfnvXAV4AlzcvUCxkVUFw3+EU= go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/multierr v1.1.0 h1:HoEmRHQPVSqub6w2z2d2EOVs2fjyFRGyofhKuyDq0QI= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/thriftrw v1.20.2 h1:0JlCE7dOyWHEQdfDm0MWIbgTn6vXkiMA6LNIe8FQXjw= +go.uber.org/thriftrw v1.20.2/go.mod h1:a0+HZMaS9tmHDCPyrrx1GjYWFRK02xzxnrK1Nl9LiLU= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= 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-20181112202954-3d3f9f413869/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= @@ -568,6 +602,7 @@ golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTk 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 h1:QzoH/1pFpZguR8NrRHLcO6jKqfv2zpuSqZLgdm7ZmjI= golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -586,15 +621,12 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190502183928-7f726cade0ab/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-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191009170851-d66e71096ffb h1:TR699M2v0qoKTOHxeLgp6zPqaQNs74f01a/ob9W0qko= -golang.org/x/net v0.0.0-20191009170851-d66e71096ffb/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 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 h1:SVwTIAaPC2U/AvvLNZ2a7OVsmBpC8L5BlwK1whH3hm0= @@ -658,6 +690,7 @@ google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEn 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 h1:QzqyMA1tlu6CgqCDUtU9V+ZKhLFT2dkJuANu5QaxI3I= 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= @@ -670,10 +703,11 @@ google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98 google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= 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 h1:j6XxA85m/6txkUCHvzlV5f+HBNl/1r5cZ2A/3IEFOO8= 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/alecthomas/kingpin.v3-unstable v3.0.0-20191105091915-95d230a53780 h1:CEBpW6C191eozfEuWdUmIAHn7lwlLxJ7HVdr2e2Tsrw= +gopkg.in/alecthomas/kingpin.v3-unstable v3.0.0-20191105091915-95d230a53780/go.mod h1:3HH7i1SgMqlzxCcBmUHW657sD4Kvv9sC3HpL3YukzwA= 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= diff --git a/internal/util/api.go b/internal/util/api.go index 5ab56e4..a5ed103 100644 --- a/internal/util/api.go +++ b/internal/util/api.go @@ -7,11 +7,8 @@ import ( apiclient "github.com/Optum/dce-cli/client" "github.com/Optum/dce-cli/client/operations" - "github.com/Optum/dce-cli/configs" "github.com/Optum/dce-cli/internal/observation" - observ "github.com/Optum/dce-cli/internal/observation" "github.com/aws/aws-sdk-go/aws/credentials" - awsSession "github.com/aws/aws-sdk-go/aws/session" sigv4 "github.com/aws/aws-sdk-go/aws/signer/v4" httptransport "github.com/go-openapi/runtime/client" "github.com/go-openapi/strfmt" @@ -20,6 +17,41 @@ import ( "net/http/httputil" ) +type NewAPIClientInput struct { + credentials *credentials.Credentials + region *string + host *string + basePath *string + token *string +} + +func NewAPIClient(input *NewAPIClientInput) *operations.Client { + // Set default region + var region string + if input.region == nil { + log.Infof("No region configured. Defaulting to us-east-1") + region = "us-east-1" + } else { + region = *input.region + } + + sig4RoundTripper := Sig4RoundTripper{ + Proxied: http.DefaultTransport, + Creds: input.credentials, + Region: region, + Logger: log, + } + sig4HTTTPClient := http.Client{Transport: &sig4RoundTripper} + httpTransport := httptransport.NewWithClient( + *input.host, + *input.basePath, + nil, + &sig4HTTTPClient, + ) + client := apiclient.New(httpTransport, strfmt.Default) + return client.Operations +} + // Adapted from https://stackoverflow.com/questions/39527847/is-there-middleware-for-go-http-client type Sig4RoundTripper struct { Proxied http.RoundTripper @@ -69,28 +101,3 @@ func (srt Sig4RoundTripper) RoundTrip(req *http.Request) (res *http.Response, e log.Debugln("Response: ", res) return res, e } - -type APIUtil struct { - Config *configs.Root - Observation *observ.ObservationContainer - Session *awsSession.Session -} - -func (u *APIUtil) InitApiClient() *operations.Client { - // Set default region - region := *u.Session.Config.Region - if region == "" { - region = "us-east-1" - } - - sig4RoundTripper := Sig4RoundTripper{ - Proxied: http.DefaultTransport, - Creds: u.Session.Config.Credentials, - Region: region, - Logger: log, - } - sig4HTTTPClient := http.Client{Transport: &sig4RoundTripper} - httpTransport := httptransport.NewWithClient(*u.Config.API.Host, *u.Config.API.BasePath, nil, &sig4HTTTPClient) - client := apiclient.New(httpTransport, strfmt.Default) - return client.Operations -} diff --git a/internal/util/filesystem.go b/internal/util/filesystem.go index 0e89a9f..88ab478 100644 --- a/internal/util/filesystem.go +++ b/internal/util/filesystem.go @@ -5,42 +5,58 @@ import ( "os" "github.com/Optum/dce-cli/configs" - observ "github.com/Optum/dce-cli/internal/observation" "github.com/mholt/archiver" "github.com/mitchellh/go-homedir" "gopkg.in/yaml.v2" ) type FileSystemUtil struct { - Config *configs.Root - Observation *observ.ObservationContainer - DefaultConfigFileName string + Config *configs.Root + ConfigFile string } -func (u *FileSystemUtil) WriteToYAMLFile(path string, _struct interface{}) { +func (u *FileSystemUtil) writeToYAMLFile(path string, _struct interface{}) error { _yaml, err := yaml.Marshal(_struct) if err != nil { - log.Fatalf("error: %v", err) + return err } if !u.IsExistingFile(path) { var file *os.File file, err = os.Create(path) if err != nil { - log.Fatalf("error: %v", err) + return err } defer file.Close() } err = ioutil.WriteFile(path, _yaml, 0644) if err != nil { - log.Fatalf("error: %v", err) + return err } + return nil +} + +// WriteConfig writes the Config objects as YAML +// to the config file location (dce.yml) +func (u *FileSystemUtil) WriteConfig() error { + return u.writeToYAMLFile(u.ConfigFile, u.Config) +} + +// ReadInConfig loads the configuration from `dce.yml` +// and unmarshals it into the config object +func (u *FileSystemUtil) ReadInConfig() error { + yamlStr, err := ioutil.ReadFile(u.ConfigFile) + if err != nil { + return err + } + + return yaml.Unmarshal(yamlStr, u.Config) } -func (u *FileSystemUtil) GetDefaultConfigFile() string { - return u.GetHomeDir() + "/" + u.DefaultConfigFileName +func (u *FileSystemUtil) GetConfigFile() string { + return u.ConfigFile } func (u *FileSystemUtil) GetHomeDir() string { @@ -74,7 +90,7 @@ func (u *FileSystemUtil) Unarchive(source string, destination string) { } } -func (s *FileSystemUtil) MvToTempDir(prefix string) (string, string) { +func (u *FileSystemUtil) MvToTempDir(prefix string) (string, string) { destinationDir, err := ioutil.TempDir("", prefix) if err != nil { log.Fatalln(err) @@ -87,28 +103,28 @@ func (s *FileSystemUtil) MvToTempDir(prefix string) (string, string) { return destinationDir, originDir } -func (s *FileSystemUtil) RemoveAll(path string) { +func (u *FileSystemUtil) RemoveAll(path string) { err := os.RemoveAll(path) if err != nil { log.Fatalln(err) } } -func (s *FileSystemUtil) Chdir(path string) { +func (u *FileSystemUtil) Chdir(path string) { err := os.Chdir(path) if err != nil { log.Fatalln(err) } } -func (s *FileSystemUtil) WriteFile(fileName string, data string) { +func (u *FileSystemUtil) WriteFile(fileName string, data string) { err := ioutil.WriteFile(fileName, []byte(data), 0644) if err != nil { log.Fatalln(err) } } -func (s *FileSystemUtil) ReadDir(path string) []os.FileInfo { +func (u *FileSystemUtil) ReadDir(path string) []os.FileInfo { files, err := ioutil.ReadDir(path) if err != nil { log.Fatalln(err) diff --git a/internal/util/session.go b/internal/util/session.go new file mode 100644 index 0000000..911c486 --- /dev/null +++ b/internal/util/session.go @@ -0,0 +1,96 @@ +package util + +import ( + "encoding/base64" + "encoding/json" + "errors" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/session" + "time" + + "github.com/aws/aws-sdk-go/aws/credentials" +) + +func NewAWSSession(token *string) (*session.Session, error) { + // Setup the AWS credentials provider chain. + // First, we'll check for credentials in the + // dce.yaml's `api.token` config. + // then we'll use AWS's standard chain (env vars, ~/aws/credentials file) + creds := credentials.NewChainCredentials([]credentials.Provider{ + NewAPITokenProvider(token), + &credentials.EnvProvider{}, + &credentials.SharedCredentialsProvider{}, + }) + return session.NewSession(&aws.Config{ + Credentials: creds, + }) +} + +// APITokenProvider is a custom AWS Credentials provider +// which uses a base64 encoded token containing a STS credentials as JSON +// See https://docs.aws.amazon.com/sdk-for-go/api/aws/credentials/#hdr-Custom_Provider +// +// Using a custom Provider does a few things for us: +// - Allows chaining credentials, so we can fall back to env vars, creds fil +// - The `Retrieve` method is cached by the client, so we don't need to re-parse our API token at every call +// - Provides a mechanism for handling expired creds +type APITokenProvider struct { + token *string + expiration int64 +} + +const APITokenProviderName = "APITokenProvider" + +func NewAPITokenProvider(token *string) credentials.Provider { + return &APITokenProvider{ + token: token, + } +} + +func (t *APITokenProvider) Retrieve() (credentials.Value, error) { + if t.token == nil { + return credentials.Value{ProviderName: APITokenProviderName}, + errors.New("no API token is configured") + } + + stsTokenJSON, err := base64.StdEncoding.DecodeString(*t.token) + if err != nil { + return credentials.Value{ProviderName: APITokenProviderName}, + errors.New("failed to decode token") + } + + // Unmarshal the STS Token JSON + var tokenValue APITokenValue + err = json.Unmarshal(stsTokenJSON, &tokenValue) + if err != nil { + return credentials.Value{ProviderName: APITokenProviderName}, + errors.New("decoded token contains invalid JSON") + } + + // Remember the tokens `expired` time, + // so we can implement `IsExpired` + t.expiration = tokenValue.Expiration + + if t.IsExpired() { + return credentials.Value{ProviderName: APITokenProviderName}, + errors.New("token is expired") + } + + return credentials.Value{ + AccessKeyID: tokenValue.AccessKeyID, + SecretAccessKey: tokenValue.SecretAccessKey, + SessionToken: tokenValue.SessionToken, + ProviderName: APITokenProviderName, + }, nil +} + +func (t *APITokenProvider) IsExpired() bool { + return time.Now().Unix() > t.expiration +} + +type APITokenValue struct { + AccessKeyID string `json:"accessKeyId"` + SecretAccessKey string + SessionToken string + Expiration int64 +} diff --git a/internal/util/util.go b/internal/util/util.go index 1a4532a..98498d6 100644 --- a/internal/util/util.go +++ b/internal/util/util.go @@ -1,18 +1,20 @@ package util import ( + "github.com/aws/aws-sdk-go/aws/session" "os" "github.com/Optum/dce-cli/configs" - "github.com/Optum/dce-cli/internal/constants" observ "github.com/Optum/dce-cli/internal/observation" - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/session" ) type UtilContainer struct { - Config *configs.Root + Config *configs.Root + // File path location of the dce.yaml file, from which this config was parsed + // Useful if we want to reload or modify the file later + ConfigFile string Observation *observ.ObservationContainer + AWSSession *session.Session AWSer APIer Terraformer @@ -25,29 +27,37 @@ type UtilContainer struct { var log observ.Logger // New returns a new Util given config -func New(config *configs.Root, observation *observ.ObservationContainer) *UtilContainer { - +func New(config *configs.Root, configFile string, observation *observ.ObservationContainer) *UtilContainer { log = observation.Logger - var session = session.New(&aws.Config{ - Region: config.Region, - }) + var awsSession *session.Session + awsSession, err := NewAWSSession(config.API.Token) + if err != nil { + log.Fatalf("Failed to initialize AWS Session: %s", err) + } - var initializedApiClient APIer - apiUtil := &APIUtil{Config: config, Observation: observation, Session: session} - if config.API.Host != nil { - initializedApiClient = apiUtil.InitApiClient() + var apiClient APIer + if config.API.Host != nil && config.API.BasePath != nil { + apiClient = NewAPIClient(&NewAPIClientInput{ + credentials: awsSession.Config.Credentials, + region: config.Region, + host: config.API.Host, + basePath: config.API.BasePath, + token: config.API.Token, + }) } + utilContainer := UtilContainer{ Config: config, Observation: observation, - AWSer: &AWSUtil{Config: config, Observation: observation, Session: session}, - APIer: initializedApiClient, + AWSSession: awsSession, + AWSer: &AWSUtil{Config: config, Observation: observation, Session: awsSession}, + APIer: apiClient, Terraformer: &TerraformUtil{Config: config, Observation: observation}, Githuber: &GithubUtil{Config: config, Observation: observation}, Prompter: &PromptUtil{Config: config, Observation: observation}, - FileSystemer: &FileSystemUtil{Config: config, Observation: observation, DefaultConfigFileName: constants.DefaultConfigFileName}, + FileSystemer: &FileSystemUtil{Config: config, ConfigFile: configFile}, Weber: &WebUtil{Observation: observation}, } @@ -76,11 +86,12 @@ type Prompter interface { } type FileSystemer interface { - WriteToYAMLFile(path string, _struct interface{}) - GetDefaultConfigFile() string + WriteConfig() error + GetConfigFile() string GetHomeDir() string IsExistingFile(path string) bool ReadFromFile(path string) string + ReadInConfig() error Unarchive(source string, destination string) MvToTempDir(prefix string) (string, string) RemoveAll(path string) diff --git a/mocks/Authenticater.go b/mocks/Authenticater.go index 9167cff..8484810 100644 --- a/mocks/Authenticater.go +++ b/mocks/Authenticater.go @@ -9,7 +9,7 @@ type Authenticater struct { mock.Mock } -// Authenticate provides a mock function with given fields: authUrl -func (_m *Authenticater) Authenticate(authUrl string) { - _m.Called(authUrl) +// Authenticate provides a mock function with given fields: +func (_m *Authenticater) Authenticate() { + _m.Called() } diff --git a/mocks/FileSystemer.go b/mocks/FileSystemer.go index 2b6466f..7e7dee2 100644 --- a/mocks/FileSystemer.go +++ b/mocks/FileSystemer.go @@ -15,8 +15,8 @@ func (_m *FileSystemer) Chdir(path string) { _m.Called(path) } -// GetDefaultConfigFile provides a mock function with given fields: -func (_m *FileSystemer) GetDefaultConfigFile() string { +// GetConfigFile provides a mock function with given fields: +func (_m *FileSystemer) GetConfigFile() string { ret := _m.Called() var r0 string @@ -108,6 +108,20 @@ func (_m *FileSystemer) ReadFromFile(path string) string { return r0 } +// ReadInConfig provides a mock function with given fields: +func (_m *FileSystemer) ReadInConfig() error { + ret := _m.Called() + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + // RemoveAll provides a mock function with given fields: path func (_m *FileSystemer) RemoveAll(path string) { _m.Called(path) @@ -118,12 +132,21 @@ func (_m *FileSystemer) Unarchive(source string, destination string) { _m.Called(source, destination) } +// WriteConfig provides a mock function with given fields: +func (_m *FileSystemer) WriteConfig() error { + ret := _m.Called() + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + // WriteFile provides a mock function with given fields: fileName, data func (_m *FileSystemer) WriteFile(fileName string, data string) { _m.Called(fileName, data) } - -// WriteToYAMLFile provides a mock function with given fields: path, _struct -func (_m *FileSystemer) WriteToYAMLFile(path string, _struct interface{}) { - _m.Called(path, _struct) -} diff --git a/mocks/Initer.go b/mocks/Initer.go index f5be19c..35f5418 100644 --- a/mocks/Initer.go +++ b/mocks/Initer.go @@ -9,7 +9,7 @@ type Initer struct { mock.Mock } -// InitializeDCE provides a mock function with given fields: cfgFile -func (_m *Initer) InitializeDCE(cfgFile string) { - _m.Called(cfgFile) +// InitializeDCE provides a mock function with given fields: +func (_m *Initer) InitializeDCE() { + _m.Called() } diff --git a/pkg/service/auth.go b/pkg/service/auth.go index 17d0e51..45d7d5a 100644 --- a/pkg/service/auth.go +++ b/pkg/service/auth.go @@ -1,9 +1,14 @@ package service import ( + "errors" + "fmt" "github.com/Optum/dce-cli/configs" observ "github.com/Optum/dce-cli/internal/observation" utl "github.com/Optum/dce-cli/internal/util" + "net/url" + "path" + "time" ) type AuthService struct { @@ -12,11 +17,43 @@ type AuthService struct { Util *utl.UtilContainer } -func (s *AuthService) Authenticate(authUrl string) { - log.Println("Opening web browser. Please Login and copy/paste the provided credentials into this terminal.") +func (s *AuthService) Authenticate() error { + // Check that our API is configured properly + if s.Config.API.Host == nil || s.Config.API.BasePath == nil { + return errors.New("Unable to authenticate against DCE API: missing API configuration") + } + + log.Println("Opening web browser. Please Login and copy/paste the provided token into this terminal.") + // Wait a moment, so the user can see our message, and know what's going on + time.Sleep(1 * time.Second) - if authUrl == "" { - authUrl = *s.Config.System.Auth.LoginURL + // Open the DCE API's /auth URL + // this will use Cognito to redirect the user to + // their configured IDP, then back to the /auth page, + // which will display a "auth code" to the end-user. + // The user will then need to copy the auth code + // into their CLI prompt. + authUrl := url.URL{ + Scheme: "https", + Host: *s.Config.API.Host, + Path: path.Join(*s.Config.API.BasePath, "/auth"), } - s.Util.OpenURL(authUrl) + s.Util.OpenURL(authUrl.String()) + + // Prompt for the auth code + authCode := s.Util.PromptBasic( + "Enter API Token: ", nil, + ) + + + // Update the dce.yml config, with the token + log.Printf("Saving API Token to %s", s.Util.GetConfigFile()) + s.Config.API.Token = authCode + err := s.Util.WriteConfig() + if err != nil { + return fmt.Errorf("Failed to write to %s: %s", + s.Util.GetConfigFile(), err) + } + + return nil } diff --git a/pkg/service/init.go b/pkg/service/init.go index a8081c9..b1e6eba 100644 --- a/pkg/service/init.go +++ b/pkg/service/init.go @@ -2,10 +2,9 @@ package service import ( "github.com/Optum/dce-cli/configs" - "github.com/Optum/dce-cli/internal/constants" observ "github.com/Optum/dce-cli/internal/observation" utl "github.com/Optum/dce-cli/internal/util" - "gopkg.in/yaml.v2" + "github.com/aws/aws-sdk-go/aws" ) type InitService struct { @@ -14,37 +13,32 @@ type InitService struct { Util *utl.UtilContainer } -func (s *InitService) InitializeDCE(cfgFile string) { - if cfgFile == "" { - cfgFile = s.Util.GetDefaultConfigFile() +func (s *InitService) InitializeDCE() { + // Set default region + if s.Config.Region == nil { + s.Config.Region = aws.String("us-east-1") } - config := s.promptUserForConfig() - yamlConfig, err := yaml.Marshal(config) + // Prompt user for required configs + s.promptUserForConfig(s.Config) + + // Write the config to dce.yml + err := s.Util.WriteConfig() if err != nil { - log.Fatalln(err) - } - log.Infoln("You have entered the following configuration:\n" + string(yamlConfig)) - if *s.Util.PromptBasic(constants.PromptChangeConfigConfirmation, nil) != "yes" { - log.Endln("Aborting") + log.Fatalf("Failed to write YAML config to %s: %s", + s.Util.GetConfigFile(), err) } - s.Util.WriteToYAMLFile(cfgFile, config) - - log.Infoln("Config file created at: " + cfgFile) + log.Infoln("Config file created at: " + s.Util.GetConfigFile()) } -func (s *InitService) promptUserForConfig() *configs.Root { - newConfig := configs.Root{} - - // System Config - newConfig.System.Auth.LoginURL = s.Util.PromptBasic("Authentication URL (SSO)", nil) - +func (s *InitService) promptUserForConfig(config *configs.Root) { // API Config - newConfig.Region = s.Util.PromptSelect("Region is DCE deployed in", configs.Regions) - newConfig.API.Host = s.Util.PromptBasic("Host name of the DCE API (example: abcde12345.execute-api.us-east-1.amazonaws.com)", nil) - newConfig.API.BasePath = s.Util.PromptBasic("Base path of the DCE API (example: /apigw-stage-name)", nil) - - newConfig.GithubToken = s.Util.PromptBasic("Github token used to download releases from github (Leave blank to use GITHUB_TOKEN env variable)", nil) - return &newConfig + if config.API.Host == nil { + config.API.Host = s.Util.PromptBasic("Host name of the DCE API (example: abcde12345.execute-api.us-east-1.amazonaws.com)", nil) + } + if config.API.BasePath == nil { + config.API.BasePath = s.Util.PromptBasic("Base path of the DCE API (example: /api)", nil) + } } + diff --git a/pkg/service/service.go b/pkg/service/service.go index 161abc1..add8621 100644 --- a/pkg/service/service.go +++ b/pkg/service/service.go @@ -79,11 +79,11 @@ type Leaser interface { } type Initer interface { - InitializeDCE(cfgFile string) + InitializeDCE() } type Authenticater interface { - Authenticate(authUrl string) + Authenticate() error } type ResponseWithPayload interface { diff --git a/tests/functional/initialization_test.go b/tests/functional/initialization_test.go deleted file mode 100644 index 75e605a..0000000 --- a/tests/functional/initialization_test.go +++ /dev/null @@ -1,140 +0,0 @@ -package functional - -import ( - "encoding/json" - "io" - "io/ioutil" - "log" - "os" - "os/exec" - "testing" - "time" - - "path/filepath" - - "github.com/Optum/dce-cli/configs" - "github.com/manifoldco/promptui" - "github.com/stretchr/testify/assert" - "gopkg.in/yaml.v2" -) - -var configFileName string - -// Reused test steps -var dceInitWritesToConfig = func(t *testing.T) { - cmd := exec.Command(testBinary, "init", "--config", configFileName) - - stdin, err := cmd.StdinPipe() - if err != nil { - t.Log(err) - } - - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - - if err = cmd.Start(); err != nil { - t.Log(err) - } - - input := user{ - stdin: stdin, - test: t, - } - doesntMatter := "doesnt matter" - usEast1 := "us-east-1" - config := configs.Root{} - config.System.Auth.LoginURL = &doesntMatter - config.Region = &usEast1 - config.API.Host = &doesntMatter - config.API.BasePath = &doesntMatter - config.GithubToken = &doesntMatter - - input.types(*config.System.Auth.LoginURL) - input.pressesEnter() - input.types(*config.API.Host) - input.types(*config.API.BasePath) - input.types(*config.GithubToken) - input.types("yes") - - stdin.Close() - - err = cmd.Wait() - if err != nil { - t.Log(err) - } - t.Run("THEN config is written to file at specified path", func(t *testing.T) { - fullConfigPath := filepath.Join(destinationDir, configFileName) - assert.FileExists(t, fullConfigPath, "Config file not found") - t.Run("AND it matches the configuration provided by the user", func(t *testing.T) { - actualStruct := configs.Root{} - actualConfigFile, _ := ioutil.ReadFile(fullConfigPath) - yaml.Unmarshal(actualConfigFile, &actualStruct) - var actualBytes []byte - actualBytes, err = json.Marshal(actualStruct) - if err != nil { - t.Log(err) - } - actualJSON := string(actualBytes) - - var exptectedBytes []byte - exptectedBytes, err = json.Marshal(&config) - expectedJSON := string(exptectedBytes) - if err != nil { - t.Log(err) - } - - assert.Equal(t, expectedJSON, actualJSON) - }) - }) -} - -func TestInitializationHappyPath(t *testing.T) { - t.Run("GIVEN custom config path flag is provided", func(t *testing.T) { - configFileName = "testConfig.yaml" - t.Run("AND config file does not exists", func(t *testing.T) { - setUp() - t.Run("WHEN user types 'dce init' with --config flag AND enters config values", dceInitWritesToConfig) - tearDown() - }) - t.Run("AND config file exists", func(t *testing.T) { - setUp() - createFile(t, configFileName) - t.Run("WHEN user types 'dce init' with --config flag AND enters config values", dceInitWritesToConfig) - tearDown() - }) - }) -} - -type user struct { - stdin io.WriteCloser - test *testing.T -} - -func (i *user) types(input string) { - i.test.Helper() - _, err := i.stdin.Write([]byte(input + string(promptui.KeyEnter))) - if err != nil { - i.test.Log(err) - } - time.Sleep(800 * time.Millisecond) -} - -func (i *user) pressesEnter() { - i.test.Helper() - _, err := i.stdin.Write([]byte(string(promptui.KeyEnter))) - if err != nil { - i.test.Log(err) - } - time.Sleep(800 * time.Millisecond) -} - -func createFile(t *testing.T, path string) { - t.Helper() - var file *os.File - var err error - file, err = os.Create(path) - if err != nil { - log.Fatalf("error: %v", err) - } - defer file.Close() -} diff --git a/tests/integration/auth_test.go b/tests/integration/auth_test.go new file mode 100644 index 0000000..d7b5b5e --- /dev/null +++ b/tests/integration/auth_test.go @@ -0,0 +1,72 @@ +package integration + +import ( + "fmt" + "github.com/Optum/dce-cli/configs" + "github.com/Optum/dce-cli/mocks" + "github.com/stretchr/testify/require" + "go.uber.org/thriftrw/ptr" + "testing" +) + +func TestAuthCommand(t *testing.T) { + + t.Run("GIVEN auth command is run", func(t *testing.T) { + + t.Run("THEN API token should be saved to config file", func(t *testing.T) { + cli := NewCLITest(t) + + // Setup a basic config file + confFile := writeTempConfig(t, &configs.Root{ + API: configs.API{ + Host: ptr.String("dce.example.com"), + BasePath: ptr.String("/api"), + }, + }) + + // Mock Weber.OpenURL() + mockWeber := &mocks.Weber{} + cli.Inject(func (input *injectorInput) { + input.service.Util.Weber = mockWeber + }) + mockWeber.On("OpenURL", "https://dce.example.com/api/auth") + + // Enter the API Token (IRL, would be provided by the web page) + cli.AnswerBasic("Enter API Token: ", "my-api-token") + + // Run DCE auth + err := cli.Execute([]string{"auth", "--config", confFile}) + require.Nil(t, err) + + cli.AssertAllPrompts() + mockWeber.AssertExpectations(t) + + output := cli.Output() + require.Contains(t, output, "Opening web browser. Please Login and copy/paste the provided token into this terminal.") + require.Contains(t, output, fmt.Sprintf("Saving API Token to %s", confFile)) + }) + + t.Run("AND no API configuration is set", func(t *testing.T) { + + t.Run("THEN auth command should fail", func(t *testing.T) { + cli := NewCLITest(t) + + // Setup a config file, missing API info + confFile := writeTempConfig(t, &configs.Root{ + API: configs.API{}, + }) + + // Run DCE auth + err := cli.Execute([]string{"auth", "--config", confFile}) + require.NotNil(t, err) + + require.Equal(t, + "Unable to authenticate against DCE API: missing API configuration", + err.Error(), + ) + }) + + }) + }) +} + diff --git a/tests/integration/init_test.go b/tests/integration/init_test.go new file mode 100644 index 0000000..54a3faf --- /dev/null +++ b/tests/integration/init_test.go @@ -0,0 +1,183 @@ +package integration + +import ( + "github.com/Optum/dce-cli/configs" + "github.com/stretchr/testify/require" + "go.uber.org/thriftrw/ptr" + "gopkg.in/yaml.v2" + "io/ioutil" + "os" + "path" + "testing" +) + +func TestInitCommand(t *testing.T) { + + t.Run("GIVEN custom config path flag is provided", func(t *testing.T) { + + t.Run("AND config file does not exists", func(t *testing.T) { + + t.Run("THEN config is written to file at specified path", func(t *testing.T) { + cli := NewCLITest(t) + + // Answer interactive CLI prompts + cli.AnswerBasic( + "Host name of the DCE API (example: abcde12345.execute-api.us-east-1.amazonaws.com)", + "dce.example.com", + ) + cli.AnswerBasic( + "Base path of the DCE API (example: /api)", + "/api", + ) + + // Create a tmp dir for our dce.yml config to live in + tmpdir, err := ioutil.TempDir("", "dce-cli-test") + require.Nil(t, err) + defer os.RemoveAll(tmpdir) + confFile := path.Join(tmpdir, "dce.yml") + + // Run `dce init` + err = cli.Execute([]string{"init", "--config", confFile}) + require.Nil(t, err) + + cli.AssertAllPrompts() + + // Check that we wrote to the dce.yml config file + assertYamlConfig(t, &configs.Root{ + API: configs.API{ + Host: ptr.String("dce.example.com"), + BasePath: ptr.String("/api"), + }, + Region: ptr.String("us-east-1"), + }, confFile) + + // Check CLI outputs + require.Contains(t, cli.Output(), "Config file created at:") + }) + + }) + + t.Run("AND config file is empty", func(t *testing.T) { + + t.Run("THEN config is written to file at specified path", func(t *testing.T) { + cli := NewCLITest(t) + + // Answer interactive CLI prompts + cli.AnswerBasic( + "Host name of the DCE API (example: abcde12345.execute-api.us-east-1.amazonaws.com)", + "dce.example.com", + ) + cli.AnswerBasic( + "Base path of the DCE API (example: /api)", + "/api", + ) + + // Create a tmp dir for our dce.yml config to live in + tmpdir, err := ioutil.TempDir("", "dce-cli-test") + require.Nil(t, err) + defer os.RemoveAll(tmpdir) + confFile := path.Join(tmpdir, "dce.yml") + + // Create empty dce.yml file + file, err := os.Create(confFile) + require.Nil(t, err) + defer file.Close() + defer os.Remove(file.Name()) + + // Run `dce init` + err = cli.Execute([]string{"init", "--config", confFile}) + require.Nil(t, err) + + cli.AssertAllPrompts() + + // Check that we wrote to the dce.yml config file + assertYamlConfig(t, &configs.Root{ + API: configs.API{ + Host: ptr.String("dce.example.com"), + BasePath: ptr.String("/api"), + }, + Region: ptr.String("us-east-1"), + }, confFile) + + // Check CLI outputs + require.Contains(t, cli.Output(), "Config file created at:") + }) + + }) + + t.Run("AND config file has existing YAML", func(t *testing.T) { + + t.Run("THEN config YAML is updated from CLI prompts", func(t *testing.T) { + confFile := writeTempConfig(t, &configs.Root{ + API: configs.API{ + Host: ptr.String("dce.example.com"), + Token: ptr.String("my-api-token"), + }, + Region: ptr.String("us-west-2"), + GithubToken: ptr.String("my-gh-token"), + }) + + cli := NewCLITest(t) + + // Answer interactive CLI prompts + // updating existing config values + // Note that we'll only prompt for any fields which aren't + // already set + cli.AnswerBasic( + "Base path of the DCE API (example: /api)", + "/api-new", + ) + + // Run `dce init` + err := cli.Execute([]string{"init", "--config", confFile}) + require.Nil(t, err) + + // Check that YAML config was updated + assertYamlConfig(t, &configs.Root{ + API: configs.API{ + Host: ptr.String("dce.example.com"), + // Should modify from CLI prompts + BasePath: ptr.String("/api-new"), + Token: ptr.String("my-api-token"), + }, + Region: ptr.String("us-west-2"), + GithubToken: ptr.String("my-gh-token"), + }, confFile) + }) + + }) + + t.Run("AND config file has invalid YAML content", func(t *testing.T) { + + t.Run("THEN command fails", func(t *testing.T) { + // Write some garbage to a file + // Create a tmp file + tmpfile, err := ioutil.TempFile("", "dce.*.yml") + require.Nil(t, err) + err = ioutil.WriteFile(tmpfile.Name(), []byte("not valid YAML"), 0644) + require.Nil(t, err) + _ = tmpfile.Close() + defer os.Remove(tmpfile.Name()) + + // Run `dce init` (should fail) + cli := NewCLITest(t) + err = cli.Execute([]string{"init", "--config", tmpfile.Name()}) + require.NotNil(t, err) + require.Contains(t, err.Error(), "Failed to parse dce.yml:") + }) + + }) + }) + +} + +func assertYamlConfig(t *testing.T, expectedConf *configs.Root, yamlFile string) { + yamlStr, err := ioutil.ReadFile(yamlFile) + require.Nilf(t, err, "Failed to read %s", yamlFile) + + var actualConf configs.Root + err = yaml.Unmarshal(yamlStr, &actualConf) + require.Nilf(t, err, "Failed to parse YAML for %s", yamlFile) + + require.Equal(t, expectedConf, &actualConf) +} diff --git a/tests/integration/mock_prompter.go b/tests/integration/mock_prompter.go new file mode 100644 index 0000000..f8ec1c0 --- /dev/null +++ b/tests/integration/mock_prompter.go @@ -0,0 +1,73 @@ +package integration + +import ( + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "reflect" + "testing" +) + +type basicAnswer struct { + question string + answer string + wasAsked bool +} +type selectAnswer struct { + question string + options []string + answer string + wasAsked bool +} +type MockPrompter struct { + T *testing.T + basicAnswers []*basicAnswer + selectAnswers []*selectAnswer +} + +func (m *MockPrompter) PromptBasic(label string, validator func(input string) error) *string { + // Find a matching answer + var answer *basicAnswer + for _, ans := range m.basicAnswers { + if ans.question == label && !ans.wasAsked { + answer = ans + break + } + } + require.NotNilf(m.T, answer, "No matching answer for prompt '%s'", label) + + answer.wasAsked = true + return &answer.answer +} + +func (m *MockPrompter) PromptSelect(label string, items []string) *string { + // Find a matching answer + var answer *selectAnswer + for _, ans := range m.selectAnswers { + if ans.question == label && reflect.DeepEqual(items, ans.options) && !ans.wasAsked { + answer = ans + break + } + } + require.NotNil(m.T, answer, "Failed to answer prompt '%s': no matching answer", label) + + answer.wasAsked = true + return &answer.answer +} + +func (m *MockPrompter) AnswerBasic(question string, answer string) { + m.basicAnswers = append(m.basicAnswers, &basicAnswer{ + question, answer, false, + }) +} + +func (m *MockPrompter) AnswerSelect(question string, expectedOptions []string, answer string) { + m.selectAnswers = append(m.selectAnswers, &selectAnswer{ + question, expectedOptions, answer, false, + }) +} + +func (m *MockPrompter) AssertAllPrompts() { + for _, answer := range m.basicAnswers { + assert.Truef(m.T, answer.wasAsked, "Prompt for '%s' was never asked", answer.question) + } +} diff --git a/tests/integration/util.go b/tests/integration/util.go new file mode 100644 index 0000000..70298be --- /dev/null +++ b/tests/integration/util.go @@ -0,0 +1,131 @@ +package integration + +import ( + "bytes" + "github.com/Optum/dce-cli/cmd" + "github.com/Optum/dce-cli/configs" + "github.com/Optum/dce-cli/internal/observation" + "github.com/Optum/dce-cli/internal/util" + utl "github.com/Optum/dce-cli/internal/util" + "github.com/Optum/dce-cli/pkg/service" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" + "github.com/stretchr/testify/require" + "go.uber.org/thriftrw/ptr" + "io/ioutil" + "testing" +) + +// cliTest is a util for running integration +// tests against the CLI +type cliTest struct { + *MockPrompter + stdout *bytes.Buffer + injector injector +} + +func (test *cliTest) Execute(args []string) error { + cmd.RootCmd.SetArgs(args) + return cmd.RootCmd.Execute() +} + +func NewCLITest(t *testing.T) *cliTest { + // Reset globals, so they don't leak between tests + cmd.Config = &configs.Root{} + cmd.Service = &service.ServiceContainer{} + cmd.Util = &utl.UtilContainer{} + cmd.Observation = &observation.ObservationContainer{} + + // Mock the Prompter, to allow use interactive inputs + prompter := &MockPrompter{T: t} + + var stdout bytes.Buffer + + + cli := &cliTest{ + MockPrompter: prompter, + stdout: &stdout, + } + + // Wrap the `PreRun` method, to inject the mock prompter, + // and out logger stdout buffer + // (global services aren't initialized until `PreRun` + preRun := cmd.RootCmd.PersistentPreRunE + cmd.RootCmd.PersistentPreRunE = func(c *cobra.Command, a []string) error { + // Run the wrapped method + err := preRun(c, a) + if err != nil { + return err + } + + // Inject the prompter + cmd.Util.Prompter = prompter + + // Tell the logger to log to our stdout buffer + logger := cmd.Log.(*observation.LogObservation).LevelLogger.(*logrus.Logger) + logger.SetOutput(&stdout) + + // Allow client to inject their own mocks + if cli.injector != nil { + cli.injector(&injectorInput{cmd.Config, cmd.Service, cmd.Util, cmd.Observation}) + } + + return nil + } + + return cli +} + +func (test *cliTest) Output() string { + return test.stdout.String() +} + +type injectorInput struct { + config *configs.Root + service *service.ServiceContainer + util *utl.UtilContainer + observation *observation.ObservationContainer +} +type injector func(input *injectorInput) + +// Inject allows for injecting dependencies before +// commands are run. The `inject` function provides +// pointers to global services used by CLI commands +// +// eg +// cli.Inject(func(input *injectorInput) { +// input.util.Weber = &mocks.Weber{} +// }) +func (test *cliTest) Inject(f injector) { + test.injector = f +} + +func writeTempConfig(t *testing.T, conf *configs.Root) string { + // Create a tmp file + tmpfile, err := ioutil.TempFile("", "dce.*.yml") + require.Nil(t, err) + + // Set default config + if conf == nil { + conf = &configs.Root{ + API: configs.API{ + Host: ptr.String("dce.example.com"), + BasePath: ptr.String("/api"), + }, + } + } + + // Write config as YAML to tmp file + fsUtil := util.FileSystemUtil{ + Config: conf, + ConfigFile: tmpfile.Name(), + } + err = fsUtil.WriteConfig() + require.Nil(t, err) + + // Close the file handle + err = tmpfile.Close() + require.Nil(t, err) + + return tmpfile.Name() +} \ No newline at end of file diff --git a/tests/unit/auth_test.go b/tests/unit/auth_test.go deleted file mode 100644 index d2b061b..0000000 --- a/tests/unit/auth_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package unit - -import ( - "testing" - - "github.com/Optum/dce-cli/configs" - "github.com/stretchr/testify/mock" -) - -func TestAuthenticate(t *testing.T) { - - t.Run("GIVEN login url is specified in config", func(t *testing.T) { - config := configs.Root{} - defaultLoginURL := "default login url" - config.System.Auth.LoginURL = &defaultLoginURL - - t.Run("WHEN Authenticate is called without a url override THEN open browser at default url", func(t *testing.T) { - initMocks(config) - mockWeber.On("OpenURL", mock.Anything) - service.Authenticate("") - mockWeber.AssertExpectations(t) - }) - - t.Run("WHEN Authenticate is called with a url override THEN open browser at override url", func(t *testing.T) { - overrideLoginURL := "override login url" - - initMocks(config) - mockWeber.On("OpenURL", overrideLoginURL) - service.Authenticate(overrideLoginURL) - mockWeber.AssertExpectations(t) - }) - }) -} diff --git a/tests/unit/init_test.go b/tests/unit/init_test.go deleted file mode 100644 index 3489bbf..0000000 --- a/tests/unit/init_test.go +++ /dev/null @@ -1,101 +0,0 @@ -package unit - -import ( - "testing" - - "github.com/Optum/dce-cli/configs" - "github.com/Optum/dce-cli/internal/constants" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" -) - -func TestInitializeDCE(t *testing.T) { - emptyConfig := configs.Root{} - - t.Run("GIVEN config path is specified", func(t *testing.T) { - providedConfigPath := "providedConfigPath" - - t.Run("WHEN InitializeDCE AND asks for confirmation AND confirmation is approved", func(t *testing.T) { - initMocks(emptyConfig) - - doesntMatter := "doesn't matter" - mockPrompter.On("PromptBasic", mock.Anything, mock.Anything).Return(&doesntMatter) - mockPrompter.On("PromptSelect", mock.Anything, mock.Anything).Return(&doesntMatter) - mockFileSystemer.On("WriteToYAMLFile", mock.Anything, mock.Anything) - - yes := "yes" - mockPrompter.On("PromptBasic", mock.Anything, mock.Anything).Return(&yes) - - // Act - service.InitializeDCE(providedConfigPath) - - mockFileSystemer.AssertNotCalled(t, "GetDefaultConfigFile") - if !mockFileSystemer.AssertExpectations(t) || !mockPrompter.AssertExpectations(t) { - t.Skip() - } - t.Run("THEN write to specified config path", func(t *testing.T) { - mockFileSystemer.AssertCalled(t, "WriteToYAMLFile", providedConfigPath, mock.Anything) - }) - }) - - t.Run("WHEN InitializeDCE AND asks for confirmation AND confirmation is not approved", func(t *testing.T) { - initMocks(emptyConfig) - - doesntMatter := "doesn't matter" - mockPrompter.On("PromptBasic", mock.Anything, mock.Anything).Return(&doesntMatter) - mockPrompter.On("PromptSelect", mock.Anything, mock.Anything).Return(&doesntMatter) - mockFileSystemer.On("WriteToYAMLFile", mock.Anything, mock.Anything) - - notYes := "not yes" - mockPrompter.On("PromptBasic", constants.PromptChangeConfigConfirmation, mock.Anything).Return(¬Yes) - - // Act - service.InitializeDCE(providedConfigPath) - - if !mockFileSystemer.AssertExpectations(t) || !mockPrompter.AssertExpectations(t) { - t.Skip() - } - t.Run("THEN end process", func(t *testing.T) { - assert.True(t, spyLogger.Ended, "Process not ended") - }) - }) - }) - - t.Run("GIVEN config path is not specified", func(t *testing.T) { - t.Run("WHEN InitializeDCE AND ask for confirmation", func(t *testing.T) { - initMocks(emptyConfig) - - doesntMatter := "doesn't matter" - mockPrompter.On("PromptBasic", mock.Anything, mock.Anything).Return(&doesntMatter) - mockPrompter.On("PromptSelect", mock.Anything, mock.Anything).Return(&doesntMatter) - mockFileSystemer.On("WriteToYAMLFile", mock.Anything, mock.Anything) - defaultConfigPath := "defaultConfigPath" - mockFileSystemer.On("GetDefaultConfigFile", mock.Anything, mock.Anything).Return(defaultConfigPath) - - t.Run("AND confirmation is approved", func(t *testing.T) { - yes := "yes" - mockPrompter.On("PromptBasic", mock.Anything, mock.Anything).Return(&yes) - service.InitializeDCE("") - if !mockFileSystemer.AssertExpectations(t) || !mockPrompter.AssertExpectations(t) { - t.Skip() - } - t.Run("THEN write to default config path", func(t *testing.T) { - mockFileSystemer.AssertCalled(t, "WriteToYAMLFile", defaultConfigPath, mock.Anything) - }) - }) - - t.Run("AND confirmation is not approved", func(t *testing.T) { - notYes := "not yes" - mockPrompter.On("PromptBasic", constants.PromptChangeConfigConfirmation, mock.Anything).Return(¬Yes) - service.InitializeDCE("") - if !mockFileSystemer.AssertExpectations(t) || !mockPrompter.AssertExpectations(t) { - t.Skip() - } - t.Run("THEN end process", func(t *testing.T) { - assert.True(t, spyLogger.Ended, "Process not ended") - }) - }) - - }) - }) -}