github.com/ricardclau/terraform@v0.6.17-0.20160519222547-283e3ae6b5a9/config.go (about)

     1  //go:generate go run ./scripts/generate-plugins.go
     2  package main
     3  
     4  import (
     5  	"fmt"
     6  	"io/ioutil"
     7  	"log"
     8  	"os"
     9  	"os/exec"
    10  	"path/filepath"
    11  	"strings"
    12  
    13  	"github.com/hashicorp/go-plugin"
    14  	"github.com/hashicorp/hcl"
    15  	"github.com/hashicorp/terraform/command"
    16  	tfplugin "github.com/hashicorp/terraform/plugin"
    17  	"github.com/hashicorp/terraform/terraform"
    18  	"github.com/kardianos/osext"
    19  	"github.com/mitchellh/cli"
    20  )
    21  
    22  // Config is the structure of the configuration for the Terraform CLI.
    23  //
    24  // This is not the configuration for Terraform itself. That is in the
    25  // "config" package.
    26  type Config struct {
    27  	Providers    map[string]string
    28  	Provisioners map[string]string
    29  
    30  	DisableCheckpoint          bool `hcl:"disable_checkpoint"`
    31  	DisableCheckpointSignature bool `hcl:"disable_checkpoint_signature"`
    32  }
    33  
    34  // BuiltinConfig is the built-in defaults for the configuration. These
    35  // can be overridden by user configurations.
    36  var BuiltinConfig Config
    37  
    38  // ContextOpts are the global ContextOpts we use to initialize the CLI.
    39  var ContextOpts terraform.ContextOpts
    40  
    41  // ConfigFile returns the default path to the configuration file.
    42  //
    43  // On Unix-like systems this is the ".terraformrc" file in the home directory.
    44  // On Windows, this is the "terraform.rc" file in the application data
    45  // directory.
    46  func ConfigFile() (string, error) {
    47  	return configFile()
    48  }
    49  
    50  // ConfigDir returns the configuration directory for Terraform.
    51  func ConfigDir() (string, error) {
    52  	return configDir()
    53  }
    54  
    55  // LoadConfig loads the CLI configuration from ".terraformrc" files.
    56  func LoadConfig(path string) (*Config, error) {
    57  	// Read the HCL file and prepare for parsing
    58  	d, err := ioutil.ReadFile(path)
    59  	if err != nil {
    60  		return nil, fmt.Errorf(
    61  			"Error reading %s: %s", path, err)
    62  	}
    63  
    64  	// Parse it
    65  	obj, err := hcl.Parse(string(d))
    66  	if err != nil {
    67  		return nil, fmt.Errorf(
    68  			"Error parsing %s: %s", path, err)
    69  	}
    70  
    71  	// Build up the result
    72  	var result Config
    73  	if err := hcl.DecodeObject(&result, obj); err != nil {
    74  		return nil, err
    75  	}
    76  
    77  	return &result, nil
    78  }
    79  
    80  // Discover plugins located on disk, and fall back on plugins baked into the
    81  // Terraform binary.
    82  //
    83  // We look in the following places for plugins:
    84  //
    85  // 1. Terraform configuration path
    86  // 2. Path where Terraform is installed
    87  // 3. Path where Terraform is invoked
    88  //
    89  // Whichever file is discoverd LAST wins.
    90  //
    91  // Finally, we look at the list of plugins compiled into Terraform. If any of
    92  // them has not been found on disk we use the internal version. This allows
    93  // users to add / replace plugins without recompiling the main binary.
    94  func (c *Config) Discover(ui cli.Ui) error {
    95  	// Look in ~/.terraform.d/plugins/
    96  	dir, err := ConfigDir()
    97  	if err != nil {
    98  		log.Printf("[ERR] Error loading config directory: %s", err)
    99  	} else {
   100  		if err := c.discover(filepath.Join(dir, "plugins")); err != nil {
   101  			return err
   102  		}
   103  	}
   104  
   105  	// Next, look in the same directory as the Terraform executable, usually
   106  	// /usr/local/bin. If found, this replaces what we found in the config path.
   107  	exePath, err := osext.Executable()
   108  	if err != nil {
   109  		log.Printf("[ERR] Error loading exe directory: %s", err)
   110  	} else {
   111  		if err := c.discover(filepath.Dir(exePath)); err != nil {
   112  			return err
   113  		}
   114  	}
   115  
   116  	// Finally look in the cwd (where we are invoke Terraform). If found, this
   117  	// replaces anything we found in the config / install paths.
   118  	if err := c.discover("."); err != nil {
   119  		return err
   120  	}
   121  
   122  	// Finally, if we have a plugin compiled into Terraform and we didn't find
   123  	// a replacement on disk, we'll just use the internal version.
   124  	for name, _ := range command.InternalProviders {
   125  		if path, found := c.Providers[name]; found {
   126  			ui.Warn(fmt.Sprintf("[WARN] %s overrides an internal plugin for %s-provider.\n"+
   127  				"  If you did not expect to see this message you will need to remove the old plugin.\n"+
   128  				"  See https://www.terraform.io/docs/internals/internal-plugins.html", path, name))
   129  		} else {
   130  
   131  			cmd, err := command.BuildPluginCommandString("provider", name)
   132  			if err != nil {
   133  				return err
   134  			}
   135  			c.Providers[name] = cmd
   136  		}
   137  	}
   138  	for name, _ := range command.InternalProvisioners {
   139  		if path, found := c.Provisioners[name]; found {
   140  			ui.Warn(fmt.Sprintf("[WARN] %s overrides an internal plugin for %s-provisioner.\n"+
   141  				"  If you did not expect to see this message you will need to remove the old plugin.\n"+
   142  				"  See https://www.terraform.io/docs/internals/internal-plugins.html", path, name))
   143  		} else {
   144  			cmd, err := command.BuildPluginCommandString("provisioner", name)
   145  			if err != nil {
   146  				return err
   147  			}
   148  			c.Provisioners[name] = cmd
   149  		}
   150  	}
   151  
   152  	return nil
   153  }
   154  
   155  // Merge merges two configurations and returns a third entirely
   156  // new configuration with the two merged.
   157  func (c1 *Config) Merge(c2 *Config) *Config {
   158  	var result Config
   159  	result.Providers = make(map[string]string)
   160  	result.Provisioners = make(map[string]string)
   161  	for k, v := range c1.Providers {
   162  		result.Providers[k] = v
   163  	}
   164  	for k, v := range c2.Providers {
   165  		result.Providers[k] = v
   166  	}
   167  	for k, v := range c1.Provisioners {
   168  		result.Provisioners[k] = v
   169  	}
   170  	for k, v := range c2.Provisioners {
   171  		result.Provisioners[k] = v
   172  	}
   173  
   174  	return &result
   175  }
   176  
   177  func (c *Config) discover(path string) error {
   178  	var err error
   179  
   180  	if !filepath.IsAbs(path) {
   181  		path, err = filepath.Abs(path)
   182  		if err != nil {
   183  			return err
   184  		}
   185  	}
   186  
   187  	err = c.discoverSingle(
   188  		filepath.Join(path, "terraform-provider-*"), &c.Providers)
   189  	if err != nil {
   190  		return err
   191  	}
   192  
   193  	err = c.discoverSingle(
   194  		filepath.Join(path, "terraform-provisioner-*"), &c.Provisioners)
   195  	if err != nil {
   196  		return err
   197  	}
   198  
   199  	return nil
   200  }
   201  
   202  func (c *Config) discoverSingle(glob string, m *map[string]string) error {
   203  	matches, err := filepath.Glob(glob)
   204  	if err != nil {
   205  		return err
   206  	}
   207  
   208  	if *m == nil {
   209  		*m = make(map[string]string)
   210  	}
   211  
   212  	for _, match := range matches {
   213  		file := filepath.Base(match)
   214  
   215  		// If the filename has a ".", trim up to there
   216  		if idx := strings.Index(file, "."); idx >= 0 {
   217  			file = file[:idx]
   218  		}
   219  
   220  		// Look for foo-bar-baz. The plugin name is "baz"
   221  		parts := strings.SplitN(file, "-", 3)
   222  		if len(parts) != 3 {
   223  			continue
   224  		}
   225  
   226  		log.Printf("[DEBUG] Discovered plugin: %s = %s", parts[2], match)
   227  		(*m)[parts[2]] = match
   228  	}
   229  
   230  	return nil
   231  }
   232  
   233  // ProviderFactories returns the mapping of prefixes to
   234  // ResourceProviderFactory that can be used to instantiate a
   235  // binary-based plugin.
   236  func (c *Config) ProviderFactories() map[string]terraform.ResourceProviderFactory {
   237  	result := make(map[string]terraform.ResourceProviderFactory)
   238  	for k, v := range c.Providers {
   239  		result[k] = c.providerFactory(v)
   240  	}
   241  
   242  	return result
   243  }
   244  
   245  func (c *Config) providerFactory(path string) terraform.ResourceProviderFactory {
   246  	// Build the plugin client configuration and init the plugin
   247  	var config plugin.ClientConfig
   248  	config.Cmd = pluginCmd(path)
   249  	config.HandshakeConfig = tfplugin.Handshake
   250  	config.Managed = true
   251  	config.Plugins = tfplugin.PluginMap
   252  	client := plugin.NewClient(&config)
   253  
   254  	return func() (terraform.ResourceProvider, error) {
   255  		// Request the RPC client so we can get the provider
   256  		// so we can build the actual RPC-implemented provider.
   257  		rpcClient, err := client.Client()
   258  		if err != nil {
   259  			return nil, err
   260  		}
   261  
   262  		raw, err := rpcClient.Dispense(tfplugin.ProviderPluginName)
   263  		if err != nil {
   264  			return nil, err
   265  		}
   266  
   267  		return raw.(terraform.ResourceProvider), nil
   268  	}
   269  }
   270  
   271  // ProvisionerFactories returns the mapping of prefixes to
   272  // ResourceProvisionerFactory that can be used to instantiate a
   273  // binary-based plugin.
   274  func (c *Config) ProvisionerFactories() map[string]terraform.ResourceProvisionerFactory {
   275  	result := make(map[string]terraform.ResourceProvisionerFactory)
   276  	for k, v := range c.Provisioners {
   277  		result[k] = c.provisionerFactory(v)
   278  	}
   279  
   280  	return result
   281  }
   282  
   283  func (c *Config) provisionerFactory(path string) terraform.ResourceProvisionerFactory {
   284  	// Build the plugin client configuration and init the plugin
   285  	var config plugin.ClientConfig
   286  	config.HandshakeConfig = tfplugin.Handshake
   287  	config.Cmd = pluginCmd(path)
   288  	config.Managed = true
   289  	config.Plugins = tfplugin.PluginMap
   290  	client := plugin.NewClient(&config)
   291  
   292  	return func() (terraform.ResourceProvisioner, error) {
   293  		rpcClient, err := client.Client()
   294  		if err != nil {
   295  			return nil, err
   296  		}
   297  
   298  		raw, err := rpcClient.Dispense(tfplugin.ProvisionerPluginName)
   299  		if err != nil {
   300  			return nil, err
   301  		}
   302  
   303  		return raw.(terraform.ResourceProvisioner), nil
   304  	}
   305  }
   306  
   307  func pluginCmd(path string) *exec.Cmd {
   308  	cmdPath := ""
   309  
   310  	// If the path doesn't contain a separator, look in the same
   311  	// directory as the Terraform executable first.
   312  	if !strings.ContainsRune(path, os.PathSeparator) {
   313  		exePath, err := osext.Executable()
   314  		if err == nil {
   315  			temp := filepath.Join(
   316  				filepath.Dir(exePath),
   317  				filepath.Base(path))
   318  
   319  			if _, err := os.Stat(temp); err == nil {
   320  				cmdPath = temp
   321  			}
   322  		}
   323  
   324  		// If we still haven't found the executable, look for it
   325  		// in the PATH.
   326  		if v, err := exec.LookPath(path); err == nil {
   327  			cmdPath = v
   328  		}
   329  	}
   330  
   331  	// No plugin binary found, so try to use an internal plugin.
   332  	if strings.Contains(path, command.TFSPACE) {
   333  		parts := strings.Split(path, command.TFSPACE)
   334  		return exec.Command(parts[0], parts[1:]...)
   335  	}
   336  
   337  	// If we still don't have a path, then just set it to the original
   338  	// given path.
   339  	if cmdPath == "" {
   340  		cmdPath = path
   341  	}
   342  
   343  	// Build the command to execute the plugin
   344  	return exec.Command(cmdPath)
   345  }