terraform/config.go

282 lines
6.7 KiB
Go

package main
import (
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"path/filepath"
"strings"
"github.com/hashicorp/hcl"
"github.com/hashicorp/terraform/plugin"
"github.com/hashicorp/terraform/terraform"
"github.com/mitchellh/osext"
)
// 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 {
Providers map[string]string
Provisioners map[string]string
DisableCheckpoint bool `hcl:"disable_checkpoint"`
DisableCheckpointSignature bool `hcl:"disable_checkpoint_signature"`
}
// BuiltinConfig is the built-in defaults for the configuration. These
// can be overridden by user configurations.
var BuiltinConfig Config
// ContextOpts are the global ContextOpts we use to initialize the CLI.
var ContextOpts terraform.ContextOpts
// 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) {
return configFile()
}
// ConfigDir returns the configuration directory for Terraform.
func ConfigDir() (string, error) {
return configDir()
}
// LoadConfig loads the CLI configuration from ".terraformrc" files.
func LoadConfig(path string) (*Config, error) {
// 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)
}
// Parse it
obj, err := hcl.Parse(string(d))
if err != nil {
return nil, fmt.Errorf(
"Error parsing %s: %s", path, err)
}
// Build up the result
var result Config
if err := hcl.DecodeObject(&result, obj); err != nil {
return nil, err
}
return &result, nil
}
// Discover discovers plugins.
//
// This looks in the directory of the executable and the CWD, in that
// order for priority.
func (c *Config) Discover() error {
// Look in the cwd.
if err := c.discover("."); err != nil {
return err
}
// Look in the plugins directory. This will override any found
// in the current directory.
dir, err := ConfigDir()
if err != nil {
log.Printf("[ERR] Error loading config directory: %s", err)
} else {
if err := c.discover(filepath.Join(dir, "plugins")); err != nil {
return err
}
}
// Next, look in the same directory as the executable. Any conflicts
// will overwrite those found in our current directory.
exePath, err := osext.Executable()
if err != nil {
log.Printf("[ERR] Error loading exe directory: %s", err)
} else {
if err := c.discover(filepath.Dir(exePath)); err != nil {
return err
}
}
return nil
}
// 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)
result.Provisioners = make(map[string]string)
for k, v := range c1.Providers {
result.Providers[k] = v
}
for k, v := range c2.Providers {
result.Providers[k] = v
}
for k, v := range c1.Provisioners {
result.Provisioners[k] = v
}
for k, v := range c2.Provisioners {
result.Provisioners[k] = v
}
return &result
}
func (c *Config) discover(path string) error {
var err error
if !filepath.IsAbs(path) {
path, err = filepath.Abs(path)
if err != nil {
return err
}
}
err = c.discoverSingle(
filepath.Join(path, "terraform-provider-*"), &c.Providers)
if err != nil {
return err
}
err = c.discoverSingle(
filepath.Join(path, "terraform-provisioner-*"), &c.Provisioners)
if err != nil {
return err
}
return nil
}
func (c *Config) discoverSingle(glob string, m *map[string]string) error {
matches, err := filepath.Glob(glob)
if err != nil {
return err
}
if *m == nil {
*m = make(map[string]string)
}
for _, match := range matches {
file := filepath.Base(match)
// If the filename has a ".", trim up to there
if idx := strings.Index(file, "."); idx >= 0 {
file = file[:idx]
}
// Look for foo-bar-baz. The plugin name is "baz"
parts := strings.SplitN(file, "-", 3)
if len(parts) != 3 {
continue
}
log.Printf("[DEBUG] Discovered plugin: %s = %s", parts[2], match)
(*m)[parts[2]] = match
}
return nil
}
// ProviderFactories returns the mapping of prefixes to
// ResourceProviderFactory that can be used to instantiate a
// binary-based plugin.
func (c *Config) ProviderFactories() map[string]terraform.ResourceProviderFactory {
result := make(map[string]terraform.ResourceProviderFactory)
for k, v := range c.Providers {
result[k] = c.providerFactory(v)
}
return result
}
func (c *Config) providerFactory(path string) terraform.ResourceProviderFactory {
// Build the plugin client configuration and init the plugin
var config plugin.ClientConfig
config.Cmd = pluginCmd(path)
config.Managed = true
client := plugin.NewClient(&config)
return func() (terraform.ResourceProvider, error) {
// Request the RPC client so we can get the provider
// so we can build the actual RPC-implemented provider.
rpcClient, err := client.Client()
if err != nil {
return nil, err
}
return rpcClient.ResourceProvider()
}
}
// ProvisionerFactories returns the mapping of prefixes to
// ResourceProvisionerFactory that can be used to instantiate a
// binary-based plugin.
func (c *Config) ProvisionerFactories() map[string]terraform.ResourceProvisionerFactory {
result := make(map[string]terraform.ResourceProvisionerFactory)
for k, v := range c.Provisioners {
result[k] = c.provisionerFactory(v)
}
return result
}
func (c *Config) provisionerFactory(path string) terraform.ResourceProvisionerFactory {
// Build the plugin client configuration and init the plugin
var config plugin.ClientConfig
config.Cmd = pluginCmd(path)
config.Managed = true
client := plugin.NewClient(&config)
return func() (terraform.ResourceProvisioner, error) {
rpcClient, err := client.Client()
if err != nil {
return nil, err
}
return rpcClient.ResourceProvisioner()
}
}
func pluginCmd(path string) *exec.Cmd {
cmdPath := ""
// If the path doesn't contain a separator, look in the same
// directory as the Terraform executable first.
if !strings.ContainsRune(path, os.PathSeparator) {
exePath, err := osext.Executable()
if err == nil {
temp := filepath.Join(
filepath.Dir(exePath),
filepath.Base(path))
if _, err := os.Stat(temp); err == nil {
cmdPath = temp
}
}
// If we still haven't found the executable, look for it
// in the PATH.
if v, err := exec.LookPath(path); err == nil {
cmdPath = v
}
}
// If we still don't have a path, then just set it to the original
// given path.
if cmdPath == "" {
cmdPath = path
}
// Build the command to execute the plugin
return exec.Command(cmdPath)
}