2016-04-04 21:11:11 -05:00
|
|
|
//go:generate go run ./scripts/generate-plugins.go
|
2014-05-25 19:39:44 -05:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2014-08-19 11:56:50 -05:00
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
2014-08-28 19:27:15 -05:00
|
|
|
"log"
|
2014-08-27 22:46:50 -05:00
|
|
|
"os"
|
2017-10-19 19:28:44 -05:00
|
|
|
"path/filepath"
|
2014-06-09 23:44:13 -05:00
|
|
|
|
2014-08-19 11:56:50 -05:00
|
|
|
"github.com/hashicorp/hcl"
|
2017-10-19 18:43:18 -05:00
|
|
|
|
2016-04-04 21:11:11 -05:00
|
|
|
"github.com/hashicorp/terraform/command"
|
2017-10-19 18:43:18 -05:00
|
|
|
"github.com/hashicorp/terraform/svchost"
|
|
|
|
"github.com/hashicorp/terraform/tfdiags"
|
2014-05-25 19:39:44 -05:00
|
|
|
)
|
|
|
|
|
2017-09-01 18:20:25 -05:00
|
|
|
const pluginCacheDirEnvVar = "TF_PLUGIN_CACHE_DIR"
|
|
|
|
|
2014-05-25 19:39:44 -05:00
|
|
|
// Config is the structure of the configuration for the Terraform CLI.
|
|
|
|
//
|
|
|
|
// This is not the configuration for Terraform itself. That is in the
|
|
|
|
// "config" package.
|
|
|
|
type Config struct {
|
2014-07-09 16:47:37 -05:00
|
|
|
Providers map[string]string
|
|
|
|
Provisioners map[string]string
|
2014-10-13 16:05:29 -05:00
|
|
|
|
|
|
|
DisableCheckpoint bool `hcl:"disable_checkpoint"`
|
|
|
|
DisableCheckpointSignature bool `hcl:"disable_checkpoint_signature"`
|
2017-09-01 18:20:25 -05:00
|
|
|
|
|
|
|
// If set, enables local caching of plugins in this directory to
|
|
|
|
// avoid repeatedly re-downloading over the Internet.
|
|
|
|
PluginCacheDir string `hcl:"plugin_cache_dir"`
|
2017-10-19 19:40:20 -05:00
|
|
|
|
2017-10-25 18:00:08 -05:00
|
|
|
Hosts map[string]*ConfigHost `hcl:"host"`
|
|
|
|
|
2017-10-19 19:40:20 -05:00
|
|
|
Credentials map[string]map[string]interface{} `hcl:"credentials"`
|
|
|
|
CredentialsHelpers map[string]*ConfigCredentialsHelper `hcl:"credentials_helper"`
|
|
|
|
}
|
|
|
|
|
2017-10-25 18:00:08 -05:00
|
|
|
// ConfigHost is the structure of the "host" nested block within the CLI
|
|
|
|
// configuration, which can be used to override the default service host
|
|
|
|
// discovery behavior for a particular hostname.
|
|
|
|
type ConfigHost struct {
|
|
|
|
Services map[string]interface{} `hcl:"services"`
|
|
|
|
}
|
|
|
|
|
2017-10-19 19:40:20 -05:00
|
|
|
// ConfigCredentialsHelper is the structure of the "credentials_helper"
|
|
|
|
// nested block within the CLI configuration.
|
|
|
|
type ConfigCredentialsHelper struct {
|
|
|
|
Args []string `hcl:"args"`
|
2014-05-25 19:39:44 -05:00
|
|
|
}
|
|
|
|
|
2014-06-09 23:44:13 -05:00
|
|
|
// BuiltinConfig is the built-in defaults for the configuration. These
|
|
|
|
// can be overridden by user configurations.
|
|
|
|
var BuiltinConfig Config
|
|
|
|
|
2017-04-13 20:05:58 -05:00
|
|
|
// PluginOverrides are paths that override discovered plugins, set from
|
|
|
|
// the config file.
|
|
|
|
var PluginOverrides command.PluginOverrides
|
2014-07-03 14:01:20 -05:00
|
|
|
|
2014-08-19 12:58:23 -05:00
|
|
|
// ConfigFile returns the default path to the configuration file.
|
|
|
|
//
|
|
|
|
// On Unix-like systems this is the ".terraformrc" file in the home directory.
|
|
|
|
// On Windows, this is the "terraform.rc" file in the application data
|
|
|
|
// directory.
|
|
|
|
func ConfigFile() (string, error) {
|
2014-08-21 00:24:35 -05:00
|
|
|
return configFile()
|
2014-08-19 12:58:23 -05:00
|
|
|
}
|
|
|
|
|
2014-09-27 14:36:13 -05:00
|
|
|
// ConfigDir returns the configuration directory for Terraform.
|
|
|
|
func ConfigDir() (string, error) {
|
|
|
|
return configDir()
|
|
|
|
}
|
|
|
|
|
2017-10-19 19:01:02 -05:00
|
|
|
// 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)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-19 19:28:44 -05:00
|
|
|
if configDir, err := ConfigDir(); err == nil {
|
|
|
|
if info, err := os.Stat(configDir); err == nil && info.IsDir() {
|
|
|
|
dirConfig, dirDiags := loadConfigDir(configDir)
|
|
|
|
diags = diags.Append(dirDiags)
|
|
|
|
config = config.Merge(dirConfig)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-19 19:01:02 -05:00
|
|
|
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{}
|
|
|
|
|
2017-10-19 19:28:44 -05:00
|
|
|
log.Printf("Loading CLI configuration from %s", path)
|
|
|
|
|
2014-08-19 11:56:50 -05:00
|
|
|
// Read the HCL file and prepare for parsing
|
|
|
|
d, err := ioutil.ReadFile(path)
|
|
|
|
if err != nil {
|
2017-10-19 19:01:02 -05:00
|
|
|
diags = diags.Append(fmt.Errorf("Error reading %s: %s", path, err))
|
|
|
|
return result, diags
|
2014-05-25 19:39:44 -05:00
|
|
|
}
|
|
|
|
|
2014-08-19 11:56:50 -05:00
|
|
|
// Parse it
|
|
|
|
obj, err := hcl.Parse(string(d))
|
2014-05-25 19:39:44 -05:00
|
|
|
if err != nil {
|
2017-10-19 19:01:02 -05:00
|
|
|
diags = diags.Append(fmt.Errorf("Error parsing %s: %s", path, err))
|
|
|
|
return result, diags
|
2014-05-25 19:39:44 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// Build up the result
|
2014-08-19 11:56:50 -05:00
|
|
|
if err := hcl.DecodeObject(&result, obj); err != nil {
|
2017-10-19 19:01:02 -05:00
|
|
|
diags = diags.Append(fmt.Errorf("Error parsing %s: %s", path, err))
|
|
|
|
return result, diags
|
2014-05-25 19:39:44 -05:00
|
|
|
}
|
|
|
|
|
2017-02-13 19:52:51 -06:00
|
|
|
// Replace all env vars
|
|
|
|
for k, v := range result.Providers {
|
|
|
|
result.Providers[k] = os.ExpandEnv(v)
|
|
|
|
}
|
|
|
|
for k, v := range result.Provisioners {
|
|
|
|
result.Provisioners[k] = os.ExpandEnv(v)
|
|
|
|
}
|
|
|
|
|
2017-09-01 18:20:25 -05:00
|
|
|
if result.PluginCacheDir != "" {
|
|
|
|
result.PluginCacheDir = os.ExpandEnv(result.PluginCacheDir)
|
|
|
|
}
|
|
|
|
|
2017-10-19 19:01:02 -05:00
|
|
|
return result, diags
|
2014-05-25 19:39:44 -05:00
|
|
|
}
|
2014-06-09 23:44:13 -05:00
|
|
|
|
2017-10-19 19:28:44 -05:00
|
|
|
func loadConfigDir(path string) (*Config, tfdiags.Diagnostics) {
|
|
|
|
var diags tfdiags.Diagnostics
|
|
|
|
result := &Config{}
|
|
|
|
|
|
|
|
entries, err := ioutil.ReadDir(path)
|
|
|
|
if err != nil {
|
|
|
|
diags = diags.Append(fmt.Errorf("Error reading %s: %s", path, err))
|
|
|
|
return result, diags
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, entry := range entries {
|
|
|
|
name := entry.Name()
|
|
|
|
// Ignoring errors here because it is used only to indicate pattern
|
|
|
|
// syntax errors, and our patterns are hard-coded here.
|
|
|
|
hclMatched, _ := filepath.Match("*.tfrc", name)
|
|
|
|
jsonMatched, _ := filepath.Match("*.tfrc.json", name)
|
|
|
|
if !(hclMatched || jsonMatched) {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
filePath := filepath.Join(path, name)
|
|
|
|
fileConfig, fileDiags := loadConfigFile(filePath)
|
|
|
|
diags = diags.Append(fileDiags)
|
|
|
|
result = result.Merge(fileConfig)
|
|
|
|
}
|
|
|
|
|
|
|
|
return result, diags
|
|
|
|
}
|
|
|
|
|
2017-09-01 18:20:25 -05:00
|
|
|
// EnvConfig returns a Config populated from environment variables.
|
|
|
|
//
|
|
|
|
// Any values specified in this config should override those set in the
|
|
|
|
// configuration file.
|
|
|
|
func EnvConfig() *Config {
|
|
|
|
config := &Config{}
|
|
|
|
|
|
|
|
if envPluginCacheDir := os.Getenv(pluginCacheDirEnvVar); envPluginCacheDir != "" {
|
|
|
|
// No Expandenv here, because expanding environment variables inside
|
|
|
|
// an environment variable would be strange and seems unnecessary.
|
|
|
|
// (User can expand variables into the value while setting it using
|
|
|
|
// standard shell features.)
|
|
|
|
config.PluginCacheDir = envPluginCacheDir
|
|
|
|
}
|
|
|
|
|
|
|
|
return config
|
|
|
|
}
|
|
|
|
|
2017-10-19 18:43:18 -05:00
|
|
|
// Validate checks for errors in the configuration that cannot be detected
|
|
|
|
// just by HCL decoding, returning any problems as diagnostics.
|
|
|
|
//
|
|
|
|
// On success, the returned diagnostics will return false from the HasErrors
|
|
|
|
// method. A non-nil diagnostics is not necessarily an error, since it may
|
|
|
|
// contain just warnings.
|
|
|
|
func (c *Config) Validate() tfdiags.Diagnostics {
|
|
|
|
var diags tfdiags.Diagnostics
|
|
|
|
|
|
|
|
if c == nil {
|
|
|
|
return diags
|
|
|
|
}
|
|
|
|
|
|
|
|
// FIXME: Right now our config parsing doesn't retain enough information
|
|
|
|
// to give proper source references to any errors. We should improve
|
|
|
|
// on this when we change the CLI config parser to use HCL2.
|
|
|
|
|
2017-10-25 18:00:08 -05:00
|
|
|
// Check that all "host" blocks have valid hostnames.
|
|
|
|
for givenHost := range c.Hosts {
|
|
|
|
_, err := svchost.ForComparison(givenHost)
|
|
|
|
if err != nil {
|
|
|
|
diags = diags.Append(
|
|
|
|
fmt.Errorf("The host %q block has an invalid hostname: %s", givenHost, err),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-19 18:43:18 -05:00
|
|
|
// Check that all "credentials" blocks have valid hostnames.
|
|
|
|
for givenHost := range c.Credentials {
|
|
|
|
_, err := svchost.ForComparison(givenHost)
|
|
|
|
if err != nil {
|
|
|
|
diags = diags.Append(
|
|
|
|
fmt.Errorf("The credentials %q block has an invalid hostname: %s", givenHost, err),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Should have zero or one "credentials_helper" blocks
|
|
|
|
if len(c.CredentialsHelpers) > 1 {
|
|
|
|
diags = diags.Append(
|
|
|
|
fmt.Errorf("No more than one credentials_helper block may be specified"),
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
return diags
|
|
|
|
}
|
|
|
|
|
2014-06-09 23:57:37 -05:00
|
|
|
// Merge merges two configurations and returns a third entirely
|
|
|
|
// new configuration with the two merged.
|
|
|
|
func (c1 *Config) Merge(c2 *Config) *Config {
|
|
|
|
var result Config
|
|
|
|
result.Providers = make(map[string]string)
|
2014-07-09 16:47:37 -05:00
|
|
|
result.Provisioners = make(map[string]string)
|
2014-06-09 23:57:37 -05:00
|
|
|
for k, v := range c1.Providers {
|
|
|
|
result.Providers[k] = v
|
|
|
|
}
|
|
|
|
for k, v := range c2.Providers {
|
2016-09-13 07:05:37 -05:00
|
|
|
if v1, ok := c1.Providers[k]; ok {
|
2016-09-14 03:19:03 -05:00
|
|
|
log.Printf("[INFO] Local %s provider configuration '%s' overrides '%s'", k, v, v1)
|
2016-09-13 07:05:37 -05:00
|
|
|
}
|
2014-06-09 23:57:37 -05:00
|
|
|
result.Providers[k] = v
|
|
|
|
}
|
2014-07-09 16:47:37 -05:00
|
|
|
for k, v := range c1.Provisioners {
|
|
|
|
result.Provisioners[k] = v
|
|
|
|
}
|
|
|
|
for k, v := range c2.Provisioners {
|
2016-09-13 07:05:37 -05:00
|
|
|
if v1, ok := c1.Provisioners[k]; ok {
|
2016-09-14 03:19:03 -05:00
|
|
|
log.Printf("[INFO] Local %s provisioner configuration '%s' overrides '%s'", k, v, v1)
|
2016-09-13 07:05:37 -05:00
|
|
|
}
|
2014-07-09 16:47:37 -05:00
|
|
|
result.Provisioners[k] = v
|
|
|
|
}
|
2016-11-17 17:51:41 -06:00
|
|
|
result.DisableCheckpoint = c1.DisableCheckpoint || c2.DisableCheckpoint
|
|
|
|
result.DisableCheckpointSignature = c1.DisableCheckpointSignature || c2.DisableCheckpointSignature
|
2014-06-09 23:57:37 -05:00
|
|
|
|
2017-09-01 18:20:25 -05:00
|
|
|
result.PluginCacheDir = c1.PluginCacheDir
|
|
|
|
if result.PluginCacheDir == "" {
|
|
|
|
result.PluginCacheDir = c2.PluginCacheDir
|
|
|
|
}
|
|
|
|
|
2017-10-25 18:00:08 -05:00
|
|
|
if (len(c1.Hosts) + len(c2.Hosts)) > 0 {
|
|
|
|
result.Hosts = make(map[string]*ConfigHost)
|
|
|
|
for name, host := range c1.Hosts {
|
|
|
|
result.Hosts[name] = host
|
|
|
|
}
|
|
|
|
for name, host := range c2.Hosts {
|
|
|
|
result.Hosts[name] = host
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-19 18:41:03 -05:00
|
|
|
if (len(c1.Credentials) + len(c2.Credentials)) > 0 {
|
|
|
|
result.Credentials = make(map[string]map[string]interface{})
|
|
|
|
for host, creds := range c1.Credentials {
|
|
|
|
result.Credentials[host] = creds
|
|
|
|
}
|
|
|
|
for host, creds := range c2.Credentials {
|
|
|
|
// We just clobber an entry from the other file right now. Will
|
|
|
|
// improve on this later using the more-robust merging behavior
|
|
|
|
// built in to HCL2.
|
|
|
|
result.Credentials[host] = creds
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (len(c1.CredentialsHelpers) + len(c2.CredentialsHelpers)) > 0 {
|
|
|
|
result.CredentialsHelpers = make(map[string]*ConfigCredentialsHelper)
|
|
|
|
for name, helper := range c1.CredentialsHelpers {
|
|
|
|
result.CredentialsHelpers[name] = helper
|
|
|
|
}
|
|
|
|
for name, helper := range c2.CredentialsHelpers {
|
|
|
|
result.CredentialsHelpers[name] = helper
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-06-09 23:57:37 -05:00
|
|
|
return &result
|
|
|
|
}
|