From 11ba1d2a4c18636bee335164c6dc987eb0cfd8f0 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Thu, 19 Oct 2017 17:01:02 -0700 Subject: [PATCH] main: factor out CLI config loading into its own function Previously we handled all of the config sources directly within the main function. We're going to make CLI config loading more complex shortly, so having this encapsulated in its own function will avoid creating even more clutter inside the main function. Along the way here we also switch from using native Go "error" to using tfdiags.Diagnostics, so that we can potentially issue warnings here too in future, and so that we can return multiple errors. --- config.go | 47 ++++++++++++++++++++++++++++------- config_test.go | 6 ++--- main.go | 67 ++++++++++++++++---------------------------------- 3 files changed, 62 insertions(+), 58 deletions(-) diff --git a/config.go b/config.go index e5b3e73ad..fc55e9f6b 100644 --- a/config.go +++ b/config.go @@ -63,26 +63,55 @@ func ConfigDir() (string, error) { return configDir() } -// LoadConfig loads the CLI configuration from ".terraformrc" files. -func LoadConfig(path string) (*Config, error) { +// LoadConfig reads the CLI configuration from the various filesystem locations +// and from the environment, returning a merged configuration along with any +// diagnostics (errors and warnings) encountered along the way. +func LoadConfig() (*Config, tfdiags.Diagnostics) { + var diags tfdiags.Diagnostics + configVal := BuiltinConfig // copy + config := &configVal + + if mainFilename, err := cliConfigFile(); err == nil { + if _, err := os.Stat(mainFilename); err == nil { + mainConfig, mainDiags := loadConfigFile(mainFilename) + diags = diags.Append(mainDiags) + config = config.Merge(mainConfig) + } + } + + if envConfig := EnvConfig(); envConfig != nil { + // envConfig takes precedence + config = envConfig.Merge(config) + } + + diags = diags.Append(config.Validate()) + + return config, diags +} + +// loadConfigFile loads the CLI configuration from ".terraformrc" files. +func loadConfigFile(path string) (*Config, tfdiags.Diagnostics) { + var diags tfdiags.Diagnostics + result := &Config{} + // Read the HCL file and prepare for parsing d, err := ioutil.ReadFile(path) if err != nil { - return nil, fmt.Errorf( - "Error reading %s: %s", path, err) + diags = diags.Append(fmt.Errorf("Error reading %s: %s", path, err)) + return result, diags } // Parse it obj, err := hcl.Parse(string(d)) if err != nil { - return nil, fmt.Errorf( - "Error parsing %s: %s", path, err) + diags = diags.Append(fmt.Errorf("Error parsing %s: %s", path, err)) + return result, diags } // Build up the result - var result Config if err := hcl.DecodeObject(&result, obj); err != nil { - return nil, err + diags = diags.Append(fmt.Errorf("Error parsing %s: %s", path, err)) + return result, diags } // Replace all env vars @@ -97,7 +126,7 @@ func LoadConfig(path string) (*Config, error) { result.PluginCacheDir = os.ExpandEnv(result.PluginCacheDir) } - return &result, nil + return result, diags } // EnvConfig returns a Config populated from environment variables. diff --git a/config_test.go b/config_test.go index 28fcec92a..479013a50 100644 --- a/config_test.go +++ b/config_test.go @@ -13,7 +13,7 @@ import ( const fixtureDir = "./test-fixtures" func TestLoadConfig(t *testing.T) { - c, err := LoadConfig(filepath.Join(fixtureDir, "config")) + c, err := loadConfigFile(filepath.Join(fixtureDir, "config")) if err != nil { t.Fatalf("err: %s", err) } @@ -34,7 +34,7 @@ func TestLoadConfig_env(t *testing.T) { defer os.Unsetenv("TFTEST") os.Setenv("TFTEST", "hello") - c, err := LoadConfig(filepath.Join(fixtureDir, "config-env")) + c, err := loadConfigFile(filepath.Join(fixtureDir, "config-env")) if err != nil { t.Fatalf("err: %s", err) } @@ -55,7 +55,7 @@ func TestLoadConfig_env(t *testing.T) { } func TestLoadConfig_credentials(t *testing.T) { - got, err := LoadConfig(filepath.Join(fixtureDir, "credentials")) + got, err := loadConfigFile(filepath.Join(fixtureDir, "credentials")) if err != nil { t.Fatal(err) } diff --git a/main.go b/main.go index 85a51c54a..77f441d20 100644 --- a/main.go +++ b/main.go @@ -17,7 +17,6 @@ import ( "github.com/hashicorp/terraform/command/format" "github.com/hashicorp/terraform/helper/logging" "github.com/hashicorp/terraform/terraform" - "github.com/hashicorp/terraform/tfdiags" "github.com/mattn/go-colorable" "github.com/mattn/go-shellwords" "github.com/mitchellh/cli" @@ -111,6 +110,8 @@ func init() { } func wrappedMain() int { + var err error + // We always need to close the DebugInfo before we exit. defer terraform.CloseDebugInfo() @@ -121,60 +122,34 @@ func wrappedMain() int { log.Printf("[INFO] Go runtime version: %s", runtime.Version()) log.Printf("[INFO] CLI args: %#v", os.Args) - // Load the configuration - config := BuiltinConfig - - // Load the configuration file if we have one, that can be used to - // define extra providers and provisioners. - clicfgFile, err := cliConfigFile() - if err != nil { - Ui.Error(fmt.Sprintf("Error loading CLI configuration: \n\n%s", err)) - return 1 - } - - if clicfgFile != "" { - usrcfg, err := LoadConfig(clicfgFile) - if err != nil { - Ui.Error(fmt.Sprintf("Error loading CLI configuration: \n\n%s", err)) - return 1 + config, diags := LoadConfig() + if len(diags) > 0 { + // Since we haven't instantiated a command.Meta yet, we need to do + // some things manually here and use some "safe" defaults for things + // that command.Meta could otherwise figure out in smarter ways. + Ui.Error("There are some problems with the CLI configuration:") + for _, diag := range diags { + earlyColor := &colorstring.Colorize{ + Colors: colorstring.DefaultColors, + Disable: true, // Disable color to be conservative until we know better + Reset: true, + } + Ui.Error(format.Diagnostic(diag, earlyColor, 78)) } - - config = *config.Merge(usrcfg) - } - - if envConfig := EnvConfig(); envConfig != nil { - // envConfig takes precedence - config = *envConfig.Merge(&config) - } - - log.Printf("[DEBUG] CLI Config is %#v", config) - - { - var diags tfdiags.Diagnostics - diags = diags.Append(config.Validate()) - if len(diags) > 0 { - Ui.Error("There are some problems with the CLI configuration:") - for _, diag := range diags { - earlyColor := &colorstring.Colorize{ - Colors: colorstring.DefaultColors, - Disable: true, // Disable color to be conservative until we know better - Reset: true, - } - Ui.Error(format.Diagnostic(diag, earlyColor, 78)) - } - if diags.HasErrors() { - Ui.Error("As a result of the above problems, Terraform may not behave as intended.\n\n") - } + if diags.HasErrors() { + Ui.Error("As a result of the above problems, Terraform may not behave as intended.\n\n") + // We continue to run anyway, since Terraform has reasonable defaults. } } + log.Printf("[DEBUG] CLI config is %#v", config) // In tests, Commands may already be set to provide mock commands if Commands == nil { - initCommands(&config) + initCommands(config) } // Run checkpoint - go runCheckpoint(&config) + go runCheckpoint(config) // Make sure we clean up any managed plugins at the end of this defer plugin.CleanupClients()