2014-05-26 02:39:44 +02:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2014-06-10 06:44:13 +02:00
|
|
|
"os/exec"
|
|
|
|
"path/filepath"
|
|
|
|
|
|
|
|
"github.com/hashicorp/terraform/plugin"
|
|
|
|
"github.com/hashicorp/terraform/rpc"
|
2014-06-09 20:53:41 +02:00
|
|
|
"github.com/hashicorp/terraform/terraform"
|
2014-05-26 02:39:44 +02:00
|
|
|
"github.com/mitchellh/go-libucl"
|
2014-06-10 06:44:13 +02:00
|
|
|
"github.com/mitchellh/osext"
|
2014-05-26 02:39:44 +02: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 23:47:37 +02:00
|
|
|
Providers map[string]string
|
|
|
|
Provisioners map[string]string
|
2014-05-26 02:39:44 +02:00
|
|
|
}
|
|
|
|
|
2014-06-10 06:44:13 +02:00
|
|
|
// BuiltinConfig is the built-in defaults for the configuration. These
|
|
|
|
// can be overridden by user configurations.
|
|
|
|
var BuiltinConfig Config
|
|
|
|
|
2014-07-03 21:01:20 +02:00
|
|
|
// ContextOpts are the global ContextOpts we use to initialize the CLI.
|
|
|
|
var ContextOpts terraform.ContextOpts
|
|
|
|
|
2014-05-26 02:39:44 +02:00
|
|
|
// Put the parse flags we use for libucl in a constant so we can get
|
|
|
|
// equally behaving parsing everywhere.
|
|
|
|
const libuclParseFlags = libucl.ParserKeyLowercase
|
|
|
|
|
2014-06-10 06:44:13 +02:00
|
|
|
func init() {
|
|
|
|
BuiltinConfig.Providers = map[string]string{
|
2014-07-19 02:20:28 +02:00
|
|
|
"aws": "terraform-provider-aws",
|
|
|
|
"digitalocean": "terraform-provider-digitalocean",
|
2014-07-23 06:03:30 +02:00
|
|
|
"heroku": "terraform-provider-heroku",
|
2014-06-10 06:44:13 +02:00
|
|
|
}
|
2014-07-09 23:47:37 +02:00
|
|
|
BuiltinConfig.Provisioners = map[string]string{
|
2014-07-10 22:30:44 +02:00
|
|
|
"local-exec": "terraform-provisioner-local-exec",
|
|
|
|
"remote-exec": "terraform-provisioner-remote-exec",
|
2014-07-15 23:37:55 +02:00
|
|
|
"file": "terraform-provisioner-file",
|
2014-07-09 23:47:37 +02:00
|
|
|
}
|
2014-06-10 06:44:13 +02:00
|
|
|
}
|
|
|
|
|
2014-05-26 02:39:44 +02:00
|
|
|
// LoadConfig loads the CLI configuration from ".terraformrc" files.
|
|
|
|
func LoadConfig(path string) (*Config, error) {
|
|
|
|
var obj *libucl.Object
|
|
|
|
|
|
|
|
// Parse the file and get the root object.
|
|
|
|
parser := libucl.NewParser(libuclParseFlags)
|
|
|
|
err := parser.AddFile(path)
|
|
|
|
if err == nil {
|
|
|
|
obj = parser.Object()
|
|
|
|
defer obj.Close()
|
|
|
|
}
|
|
|
|
defer parser.Close()
|
|
|
|
|
|
|
|
// If there was an error parsing, return now.
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Build up the result
|
|
|
|
var result Config
|
|
|
|
|
|
|
|
if err := obj.Decode(&result); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &result, nil
|
|
|
|
}
|
2014-06-10 06:44:13 +02:00
|
|
|
|
2014-06-10 06:57:37 +02: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 23:47:37 +02:00
|
|
|
result.Provisioners = make(map[string]string)
|
2014-06-10 06:57:37 +02:00
|
|
|
for k, v := range c1.Providers {
|
|
|
|
result.Providers[k] = v
|
|
|
|
}
|
|
|
|
for k, v := range c2.Providers {
|
|
|
|
result.Providers[k] = v
|
|
|
|
}
|
2014-07-09 23:47:37 +02:00
|
|
|
for k, v := range c1.Provisioners {
|
|
|
|
result.Provisioners[k] = v
|
|
|
|
}
|
|
|
|
for k, v := range c2.Provisioners {
|
|
|
|
result.Provisioners[k] = v
|
|
|
|
}
|
2014-06-10 06:57:37 +02:00
|
|
|
|
|
|
|
return &result
|
|
|
|
}
|
|
|
|
|
2014-06-10 06:44:13 +02:00
|
|
|
// 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 {
|
|
|
|
originalPath := path
|
|
|
|
|
|
|
|
return func() (terraform.ResourceProvider, error) {
|
|
|
|
// First look for the provider on the PATH.
|
|
|
|
path, err := exec.LookPath(path)
|
|
|
|
if err != nil {
|
|
|
|
// If that doesn't work, look for it in the same directory
|
|
|
|
// as the executable that is running.
|
|
|
|
exePath, err := osext.Executable()
|
|
|
|
if err == nil {
|
|
|
|
path = filepath.Join(
|
|
|
|
filepath.Dir(exePath),
|
|
|
|
filepath.Base(originalPath))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we still don't have a path set, then set it to the
|
|
|
|
// original path and let any errors that happen bubble out.
|
|
|
|
if path == "" {
|
|
|
|
path = originalPath
|
|
|
|
}
|
|
|
|
|
|
|
|
// Build the plugin client configuration and init the plugin
|
|
|
|
var config plugin.ClientConfig
|
|
|
|
config.Cmd = exec.Command(path)
|
|
|
|
config.Managed = true
|
|
|
|
client := plugin.NewClient(&config)
|
|
|
|
|
|
|
|
// Request the RPC client and service name from the client
|
|
|
|
// so we can build the actual RPC-implemented provider.
|
|
|
|
rpcClient, err := client.Client()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
service, err := client.Service()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &rpc.ResourceProvider{
|
|
|
|
Client: rpcClient,
|
|
|
|
Name: service,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
}
|
2014-07-09 23:47:37 +02:00
|
|
|
|
|
|
|
// 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 {
|
|
|
|
originalPath := path
|
|
|
|
|
|
|
|
return func() (terraform.ResourceProvisioner, error) {
|
|
|
|
// First look for the provider on the PATH.
|
|
|
|
path, err := exec.LookPath(path)
|
|
|
|
if err != nil {
|
|
|
|
// If that doesn't work, look for it in the same directory
|
|
|
|
// as the executable that is running.
|
|
|
|
exePath, err := osext.Executable()
|
|
|
|
if err == nil {
|
|
|
|
path = filepath.Join(
|
|
|
|
filepath.Dir(exePath),
|
|
|
|
filepath.Base(originalPath))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we still don't have a path set, then set it to the
|
|
|
|
// original path and let any errors that happen bubble out.
|
|
|
|
if path == "" {
|
|
|
|
path = originalPath
|
|
|
|
}
|
|
|
|
|
|
|
|
// Build the plugin client configuration and init the plugin
|
|
|
|
var config plugin.ClientConfig
|
|
|
|
config.Cmd = exec.Command(path)
|
|
|
|
config.Managed = true
|
|
|
|
client := plugin.NewClient(&config)
|
|
|
|
|
|
|
|
// Request the RPC client and service name from the client
|
|
|
|
// so we can build the actual RPC-implemented provider.
|
|
|
|
rpcClient, err := client.Client()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
service, err := client.Service()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return &rpc.ResourceProvisioner{
|
|
|
|
Client: rpcClient,
|
|
|
|
Name: service,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
}
|