github.com/jgadling/terraform@v0.3.8-0.20150227214559-abd68c2c87bc/config.go (about)

     1  package main
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"log"
     7  	"os"
     8  	"os/exec"
     9  	"path/filepath"
    10  	"strings"
    11  
    12  	"github.com/hashicorp/hcl"
    13  	"github.com/hashicorp/terraform/plugin"
    14  	"github.com/hashicorp/terraform/terraform"
    15  	"github.com/mitchellh/osext"
    16  )
    17  
    18  // Config is the structure of the configuration for the Terraform CLI.
    19  //
    20  // This is not the configuration for Terraform itself. That is in the
    21  // "config" package.
    22  type Config struct {
    23  	Providers    map[string]string
    24  	Provisioners map[string]string
    25  
    26  	DisableCheckpoint          bool `hcl:"disable_checkpoint"`
    27  	DisableCheckpointSignature bool `hcl:"disable_checkpoint_signature"`
    28  }
    29  
    30  // BuiltinConfig is the built-in defaults for the configuration. These
    31  // can be overridden by user configurations.
    32  var BuiltinConfig Config
    33  
    34  // ContextOpts are the global ContextOpts we use to initialize the CLI.
    35  var ContextOpts terraform.ContextOpts
    36  
    37  // ConfigFile returns the default path to the configuration file.
    38  //
    39  // On Unix-like systems this is the ".terraformrc" file in the home directory.
    40  // On Windows, this is the "terraform.rc" file in the application data
    41  // directory.
    42  func ConfigFile() (string, error) {
    43  	return configFile()
    44  }
    45  
    46  // ConfigDir returns the configuration directory for Terraform.
    47  func ConfigDir() (string, error) {
    48  	return configDir()
    49  }
    50  
    51  // LoadConfig loads the CLI configuration from ".terraformrc" files.
    52  func LoadConfig(path string) (*Config, error) {
    53  	// Read the HCL file and prepare for parsing
    54  	d, err := ioutil.ReadFile(path)
    55  	if err != nil {
    56  		return nil, fmt.Errorf(
    57  			"Error reading %s: %s", path, err)
    58  	}
    59  
    60  	// Parse it
    61  	obj, err := hcl.Parse(string(d))
    62  	if err != nil {
    63  		return nil, fmt.Errorf(
    64  			"Error parsing %s: %s", path, err)
    65  	}
    66  
    67  	// Build up the result
    68  	var result Config
    69  	if err := hcl.DecodeObject(&result, obj); err != nil {
    70  		return nil, err
    71  	}
    72  
    73  	return &result, nil
    74  }
    75  
    76  // Discover discovers plugins.
    77  //
    78  // This looks in the directory of the executable and the CWD, in that
    79  // order for priority.
    80  func (c *Config) Discover() error {
    81  	// Look in the cwd.
    82  	if err := c.discover("."); err != nil {
    83  		return err
    84  	}
    85  
    86  	// Look in the plugins directory. This will override any found
    87  	// in the current directory.
    88  	dir, err := ConfigDir()
    89  	if err != nil {
    90  		log.Printf("[ERR] Error loading config directory: %s", err)
    91  	} else {
    92  		if err := c.discover(filepath.Join(dir, "plugins")); err != nil {
    93  			return err
    94  		}
    95  	}
    96  
    97  	// Next, look in the same directory as the executable. Any conflicts
    98  	// will overwrite those found in our current directory.
    99  	exePath, err := osext.Executable()
   100  	if err != nil {
   101  		log.Printf("[ERR] Error loading exe directory: %s", err)
   102  	} else {
   103  		if err := c.discover(filepath.Dir(exePath)); err != nil {
   104  			return err
   105  		}
   106  	}
   107  
   108  	return nil
   109  }
   110  
   111  // Merge merges two configurations and returns a third entirely
   112  // new configuration with the two merged.
   113  func (c1 *Config) Merge(c2 *Config) *Config {
   114  	var result Config
   115  	result.Providers = make(map[string]string)
   116  	result.Provisioners = make(map[string]string)
   117  	for k, v := range c1.Providers {
   118  		result.Providers[k] = v
   119  	}
   120  	for k, v := range c2.Providers {
   121  		result.Providers[k] = v
   122  	}
   123  	for k, v := range c1.Provisioners {
   124  		result.Provisioners[k] = v
   125  	}
   126  	for k, v := range c2.Provisioners {
   127  		result.Provisioners[k] = v
   128  	}
   129  
   130  	return &result
   131  }
   132  
   133  func (c *Config) discover(path string) error {
   134  	var err error
   135  
   136  	if !filepath.IsAbs(path) {
   137  		path, err = filepath.Abs(path)
   138  		if err != nil {
   139  			return err
   140  		}
   141  	}
   142  
   143  	err = c.discoverSingle(
   144  		filepath.Join(path, "terraform-provider-*"), &c.Providers)
   145  	if err != nil {
   146  		return err
   147  	}
   148  
   149  	err = c.discoverSingle(
   150  		filepath.Join(path, "terraform-provisioner-*"), &c.Provisioners)
   151  	if err != nil {
   152  		return err
   153  	}
   154  
   155  	return nil
   156  }
   157  
   158  func (c *Config) discoverSingle(glob string, m *map[string]string) error {
   159  	matches, err := filepath.Glob(glob)
   160  	if err != nil {
   161  		return err
   162  	}
   163  
   164  	if *m == nil {
   165  		*m = make(map[string]string)
   166  	}
   167  
   168  	for _, match := range matches {
   169  		file := filepath.Base(match)
   170  
   171  		// If the filename has a ".", trim up to there
   172  		if idx := strings.Index(file, "."); idx >= 0 {
   173  			file = file[:idx]
   174  		}
   175  
   176  		// Look for foo-bar-baz. The plugin name is "baz"
   177  		parts := strings.SplitN(file, "-", 3)
   178  		if len(parts) != 3 {
   179  			continue
   180  		}
   181  
   182  		log.Printf("[DEBUG] Discoverd plugin: %s = %s", parts[2], match)
   183  		(*m)[parts[2]] = match
   184  	}
   185  
   186  	return nil
   187  }
   188  
   189  // ProviderFactories returns the mapping of prefixes to
   190  // ResourceProviderFactory that can be used to instantiate a
   191  // binary-based plugin.
   192  func (c *Config) ProviderFactories() map[string]terraform.ResourceProviderFactory {
   193  	result := make(map[string]terraform.ResourceProviderFactory)
   194  	for k, v := range c.Providers {
   195  		result[k] = c.providerFactory(v)
   196  	}
   197  
   198  	return result
   199  }
   200  
   201  func (c *Config) providerFactory(path string) terraform.ResourceProviderFactory {
   202  	// Build the plugin client configuration and init the plugin
   203  	var config plugin.ClientConfig
   204  	config.Cmd = pluginCmd(path)
   205  	config.Managed = true
   206  	client := plugin.NewClient(&config)
   207  
   208  	return func() (terraform.ResourceProvider, error) {
   209  		// Request the RPC client so we can get the provider
   210  		// so we can build the actual RPC-implemented provider.
   211  		rpcClient, err := client.Client()
   212  		if err != nil {
   213  			return nil, err
   214  		}
   215  
   216  		return rpcClient.ResourceProvider()
   217  	}
   218  }
   219  
   220  // ProvisionerFactories returns the mapping of prefixes to
   221  // ResourceProvisionerFactory that can be used to instantiate a
   222  // binary-based plugin.
   223  func (c *Config) ProvisionerFactories() map[string]terraform.ResourceProvisionerFactory {
   224  	result := make(map[string]terraform.ResourceProvisionerFactory)
   225  	for k, v := range c.Provisioners {
   226  		result[k] = c.provisionerFactory(v)
   227  	}
   228  
   229  	return result
   230  }
   231  
   232  func (c *Config) provisionerFactory(path string) terraform.ResourceProvisionerFactory {
   233  	// Build the plugin client configuration and init the plugin
   234  	var config plugin.ClientConfig
   235  	config.Cmd = pluginCmd(path)
   236  	config.Managed = true
   237  	client := plugin.NewClient(&config)
   238  
   239  	return func() (terraform.ResourceProvisioner, error) {
   240  		rpcClient, err := client.Client()
   241  		if err != nil {
   242  			return nil, err
   243  		}
   244  
   245  		return rpcClient.ResourceProvisioner()
   246  	}
   247  }
   248  
   249  func pluginCmd(path string) *exec.Cmd {
   250  	cmdPath := ""
   251  
   252  	// If the path doesn't contain a separator, look in the same
   253  	// directory as the Terraform executable first.
   254  	if !strings.ContainsRune(path, os.PathSeparator) {
   255  		exePath, err := osext.Executable()
   256  		if err == nil {
   257  			temp := filepath.Join(
   258  				filepath.Dir(exePath),
   259  				filepath.Base(path))
   260  
   261  			if _, err := os.Stat(temp); err == nil {
   262  				cmdPath = temp
   263  			}
   264  		}
   265  
   266  		// If we still haven't found the executable, look for it
   267  		// in the PATH.
   268  		if v, err := exec.LookPath(path); err == nil {
   269  			cmdPath = v
   270  		}
   271  	}
   272  
   273  	// If we still don't have a path, then just set it to the original
   274  	// given path.
   275  	if cmdPath == "" {
   276  		cmdPath = path
   277  	}
   278  
   279  	// Build the command to execute the plugin
   280  	return exec.Command(cmdPath)
   281  }