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()
}
// 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.

View File

@ -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)
}

45
main.go
View File

@ -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,38 +122,11 @@ 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 = *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())
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{
@ -164,17 +138,18 @@ func wrappedMain() int {
}
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()