From 35a058fb3ddfae9cfee0b3893822c9a95b920f4c Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Thu, 19 Oct 2017 17:40:20 -0700 Subject: [PATCH] main: configure credentials from the CLI config file --- commands.go | 47 ++++++++++++++++++++++++++++++++++++--- config.go | 9 ++++++++ config_test.go | 30 +++++++++++++++++++++++++ test-fixtures/credentials | 17 ++++++++++++++ 4 files changed, 100 insertions(+), 3 deletions(-) create mode 100644 test-fixtures/credentials diff --git a/commands.go b/commands.go index b3380884d..39e30220d 100644 --- a/commands.go +++ b/commands.go @@ -4,10 +4,11 @@ import ( "os" "os/signal" + "github.com/hashicorp/terraform/command" + pluginDiscovery "github.com/hashicorp/terraform/plugin/discovery" + "github.com/hashicorp/terraform/svchost" "github.com/hashicorp/terraform/svchost/auth" "github.com/hashicorp/terraform/svchost/disco" - - "github.com/hashicorp/terraform/command" "github.com/mitchellh/cli" ) @@ -34,7 +35,7 @@ func initCommands(config *Config) { inAutomation = true } - credsSrc := auth.NoCredentials // TODO: Actually expose credentials here + credsSrc := credentialsSource(config) services := disco.NewDisco() services.SetCredentialsSource(credsSrc) @@ -342,3 +343,43 @@ func makeShutdownCh() <-chan struct{} { return resultCh } + +func credentialsSource(config *Config) auth.CredentialsSource { + creds := auth.NoCredentials + if len(config.Credentials) > 0 { + staticTable := map[svchost.Hostname]map[string]interface{}{} + for userHost, creds := range config.Credentials { + host, err := svchost.ForComparison(userHost) + if err != nil { + // We expect the config was already validated by the time we get + // here, so we'll just ignore invalid hostnames. + continue + } + staticTable[host] = creds + } + creds = auth.StaticCredentialsSource(staticTable) + } + + for helperType, helperConfig := range config.CredentialsHelpers { + available := pluginDiscovery.FindPlugins("credentials", globalPluginDirs()) + available = available.WithName(helperType) + if available.Count() == 0 { + break + } + + selected := available.Newest() + + helperSource := auth.HelperProgramCredentialsSource(selected.Path, helperConfig.Args...) + creds = auth.Credentials{ + creds, + auth.CachingCredentialsSource(helperSource), // cached because external operation may be slow/expensive + } + + // There should only be zero or one "credentials_helper" blocks. We + // assume that the config was validated earlier and so we don't check + // for extras here. + break + } + + return creds +} diff --git a/config.go b/config.go index 518832e46..abfa9d6b6 100644 --- a/config.go +++ b/config.go @@ -27,6 +27,15 @@ type Config struct { // If set, enables local caching of plugins in this directory to // avoid repeatedly re-downloading over the Internet. PluginCacheDir string `hcl:"plugin_cache_dir"` + + Credentials map[string]map[string]interface{} `hcl:"credentials"` + CredentialsHelpers map[string]*ConfigCredentialsHelper `hcl:"credentials_helper"` +} + +// ConfigCredentialsHelper is the structure of the "credentials_helper" +// nested block within the CLI configuration. +type ConfigCredentialsHelper struct { + Args []string `hcl:"args"` } // BuiltinConfig is the built-in defaults for the configuration. These diff --git a/config_test.go b/config_test.go index 712504b98..d387577ef 100644 --- a/config_test.go +++ b/config_test.go @@ -5,6 +5,8 @@ import ( "path/filepath" "reflect" "testing" + + "github.com/davecgh/go-spew/spew" ) // This is the directory where our test fixtures are. @@ -52,6 +54,34 @@ func TestLoadConfig_env(t *testing.T) { } } +func TestLoadConfig_credentials(t *testing.T) { + got, err := LoadConfig(filepath.Join(fixtureDir, "credentials")) + if err != nil { + t.Fatal(err) + } + + want := &Config{ + Credentials: map[string]map[string]interface{}{ + "example.com": map[string]interface{}{ + "token": "foo the bar baz", + }, + "example.net": map[string]interface{}{ + "username": "foo", + "password": "baz", + }, + }, + CredentialsHelpers: map[string]*ConfigCredentialsHelper{ + "foo": &ConfigCredentialsHelper{ + Args: []string{"bar", "baz"}, + }, + }, + } + + if !reflect.DeepEqual(got, want) { + t.Errorf("wrong result\ngot: %swant: %s", spew.Sdump(got), spew.Sdump(want)) + } +} + func TestConfig_Merge(t *testing.T) { c1 := &Config{ Providers: map[string]string{ diff --git a/test-fixtures/credentials b/test-fixtures/credentials new file mode 100644 index 000000000..404c4918b --- /dev/null +++ b/test-fixtures/credentials @@ -0,0 +1,17 @@ + +credentials "example.com" { + token = "foo the bar baz" +} + +credentials "example.net" { + # Username and password are not currently supported, but we want to tolerate + # unknown keys in case future versions add new keys when both old and new + # versions of Terraform are installed on a system, sharing the same + # CLI config. + username = "foo" + password = "baz" +} + +credentials_helper "foo" { + args = ["bar", "baz"] +}