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.
This commit is contained in:
Martin Atkins 2017-10-19 17:01:02 -07:00
parent a2c59c6ecd
commit 11ba1d2a4c
3 changed files with 62 additions and 58 deletions

View File

@ -63,26 +63,55 @@ func ConfigDir() (string, error) {
return configDir() return configDir()
} }
// LoadConfig loads the CLI configuration from ".terraformrc" files. // LoadConfig reads the CLI configuration from the various filesystem locations
func LoadConfig(path string) (*Config, error) { // 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 // Read the HCL file and prepare for parsing
d, err := ioutil.ReadFile(path) d, err := ioutil.ReadFile(path)
if err != nil { if err != nil {
return nil, fmt.Errorf( diags = diags.Append(fmt.Errorf("Error reading %s: %s", path, err))
"Error reading %s: %s", path, err) return result, diags
} }
// Parse it // Parse it
obj, err := hcl.Parse(string(d)) obj, err := hcl.Parse(string(d))
if err != nil { if err != nil {
return nil, fmt.Errorf( diags = diags.Append(fmt.Errorf("Error parsing %s: %s", path, err))
"Error parsing %s: %s", path, err) return result, diags
} }
// Build up the result // Build up the result
var result Config
if err := hcl.DecodeObject(&result, obj); err != nil { 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 // Replace all env vars
@ -97,7 +126,7 @@ func LoadConfig(path string) (*Config, error) {
result.PluginCacheDir = os.ExpandEnv(result.PluginCacheDir) result.PluginCacheDir = os.ExpandEnv(result.PluginCacheDir)
} }
return &result, nil return result, diags
} }
// EnvConfig returns a Config populated from environment variables. // EnvConfig returns a Config populated from environment variables.

View File

@ -13,7 +13,7 @@ import (
const fixtureDir = "./test-fixtures" const fixtureDir = "./test-fixtures"
func TestLoadConfig(t *testing.T) { func TestLoadConfig(t *testing.T) {
c, err := LoadConfig(filepath.Join(fixtureDir, "config")) c, err := loadConfigFile(filepath.Join(fixtureDir, "config"))
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
@ -34,7 +34,7 @@ func TestLoadConfig_env(t *testing.T) {
defer os.Unsetenv("TFTEST") defer os.Unsetenv("TFTEST")
os.Setenv("TFTEST", "hello") os.Setenv("TFTEST", "hello")
c, err := LoadConfig(filepath.Join(fixtureDir, "config-env")) c, err := loadConfigFile(filepath.Join(fixtureDir, "config-env"))
if err != nil { if err != nil {
t.Fatalf("err: %s", err) t.Fatalf("err: %s", err)
} }
@ -55,7 +55,7 @@ func TestLoadConfig_env(t *testing.T) {
} }
func TestLoadConfig_credentials(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 { if err != nil {
t.Fatal(err) t.Fatal(err)
} }

67
main.go
View File

@ -17,7 +17,6 @@ import (
"github.com/hashicorp/terraform/command/format" "github.com/hashicorp/terraform/command/format"
"github.com/hashicorp/terraform/helper/logging" "github.com/hashicorp/terraform/helper/logging"
"github.com/hashicorp/terraform/terraform" "github.com/hashicorp/terraform/terraform"
"github.com/hashicorp/terraform/tfdiags"
"github.com/mattn/go-colorable" "github.com/mattn/go-colorable"
"github.com/mattn/go-shellwords" "github.com/mattn/go-shellwords"
"github.com/mitchellh/cli" "github.com/mitchellh/cli"
@ -111,6 +110,8 @@ func init() {
} }
func wrappedMain() int { func wrappedMain() int {
var err error
// We always need to close the DebugInfo before we exit. // We always need to close the DebugInfo before we exit.
defer terraform.CloseDebugInfo() defer terraform.CloseDebugInfo()
@ -121,60 +122,34 @@ func wrappedMain() int {
log.Printf("[INFO] Go runtime version: %s", runtime.Version()) log.Printf("[INFO] Go runtime version: %s", runtime.Version())
log.Printf("[INFO] CLI args: %#v", os.Args) log.Printf("[INFO] CLI args: %#v", os.Args)
// Load the configuration config, diags := LoadConfig()
config := BuiltinConfig if len(diags) > 0 {
// Since we haven't instantiated a command.Meta yet, we need to do
// Load the configuration file if we have one, that can be used to // some things manually here and use some "safe" defaults for things
// define extra providers and provisioners. // that command.Meta could otherwise figure out in smarter ways.
clicfgFile, err := cliConfigFile() Ui.Error("There are some problems with the CLI configuration:")
if err != nil { for _, diag := range diags {
Ui.Error(fmt.Sprintf("Error loading CLI configuration: \n\n%s", err)) earlyColor := &colorstring.Colorize{
return 1 Colors: colorstring.DefaultColors,
} Disable: true, // Disable color to be conservative until we know better
Reset: true,
if clicfgFile != "" { }
usrcfg, err := LoadConfig(clicfgFile) Ui.Error(format.Diagnostic(diag, earlyColor, 78))
if err != nil {
Ui.Error(fmt.Sprintf("Error loading CLI configuration: \n\n%s", err))
return 1
} }
if diags.HasErrors() {
config = *config.Merge(usrcfg) 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.
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")
}
} }
} }
log.Printf("[DEBUG] CLI config is %#v", config)
// In tests, Commands may already be set to provide mock commands // In tests, Commands may already be set to provide mock commands
if Commands == nil { if Commands == nil {
initCommands(&config) initCommands(config)
} }
// Run checkpoint // Run checkpoint
go runCheckpoint(&config) go runCheckpoint(config)
// Make sure we clean up any managed plugins at the end of this // Make sure we clean up any managed plugins at the end of this
defer plugin.CleanupClients() defer plugin.CleanupClients()