github.com/ns1/terraform@v0.7.10-0.20161109153551-8949419bef40/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. Only do this
   124  	// from the main process, or the log output will break the plugin handshake.
   125  	if os.Getenv("TF_PLUGIN_MAGIC_COOKIE") == "" {
   126  		for name, _ := range command.InternalProviders {
   127  			if path, found := c.Providers[name]; found {
   128  				// Allow these warnings to be suppressed via TF_PLUGIN_DEV=1 or similar
   129  				if os.Getenv("TF_PLUGIN_DEV") == "" {
   130  					ui.Warn(fmt.Sprintf("[WARN] %s overrides an internal plugin for %s-provider.\n"+
   131  						"  If you did not expect to see this message you will need to remove the old plugin.\n"+
   132  						"  See https://www.terraform.io/docs/internals/internal-plugins.html", path, name))
   133  				}
   134  			} else {
   135  				cmd, err := command.BuildPluginCommandString("provider", name)
   136  				if err != nil {
   137  					return err
   138  				}
   139  				c.Providers[name] = cmd
   140  			}
   141  		}
   142  		for name, _ := range command.InternalProvisioners {
   143  			if path, found := c.Provisioners[name]; found {
   144  				if os.Getenv("TF_PLUGIN_DEV") == "" {
   145  					ui.Warn(fmt.Sprintf("[WARN] %s overrides an internal plugin for %s-provisioner.\n"+
   146  						"  If you did not expect to see this message you will need to remove the old plugin.\n"+
   147  						"  See https://www.terraform.io/docs/internals/internal-plugins.html", path, name))
   148  				}
   149  			} else {
   150  				cmd, err := command.BuildPluginCommandString("provisioner", name)
   151  				if err != nil {
   152  					return err
   153  				}
   154  				c.Provisioners[name] = cmd
   155  			}
   156  		}
   157  	}
   158  
   159  	return nil
   160  }
   161  
   162  // Merge merges two configurations and returns a third entirely
   163  // new configuration with the two merged.
   164  func (c1 *Config) Merge(c2 *Config) *Config {
   165  	var result Config
   166  	result.Providers = make(map[string]string)
   167  	result.Provisioners = make(map[string]string)
   168  	for k, v := range c1.Providers {
   169  		result.Providers[k] = v
   170  	}
   171  	for k, v := range c2.Providers {
   172  		if v1, ok := c1.Providers[k]; ok {
   173  			log.Printf("[INFO] Local %s provider configuration '%s' overrides '%s'", k, v, v1)
   174  		}
   175  		result.Providers[k] = v
   176  	}
   177  	for k, v := range c1.Provisioners {
   178  		result.Provisioners[k] = v
   179  	}
   180  	for k, v := range c2.Provisioners {
   181  		if v1, ok := c1.Provisioners[k]; ok {
   182  			log.Printf("[INFO] Local %s provisioner configuration '%s' overrides '%s'", k, v, v1)
   183  		}
   184  		result.Provisioners[k] = v
   185  	}
   186  
   187  	return &result
   188  }
   189  
   190  func (c *Config) discover(path string) error {
   191  	var err error
   192  
   193  	if !filepath.IsAbs(path) {
   194  		path, err = filepath.Abs(path)
   195  		if err != nil {
   196  			return err
   197  		}
   198  	}
   199  
   200  	err = c.discoverSingle(
   201  		filepath.Join(path, "terraform-provider-*"), &c.Providers)
   202  	if err != nil {
   203  		return err
   204  	}
   205  
   206  	err = c.discoverSingle(
   207  		filepath.Join(path, "terraform-provisioner-*"), &c.Provisioners)
   208  	if err != nil {
   209  		return err
   210  	}
   211  
   212  	return nil
   213  }
   214  
   215  func (c *Config) discoverSingle(glob string, m *map[string]string) error {
   216  	matches, err := filepath.Glob(glob)
   217  	if err != nil {
   218  		return err
   219  	}
   220  
   221  	if *m == nil {
   222  		*m = make(map[string]string)
   223  	}
   224  
   225  	for _, match := range matches {
   226  		file := filepath.Base(match)
   227  
   228  		// If the filename has a ".", trim up to there
   229  		if idx := strings.Index(file, "."); idx >= 0 {
   230  			file = file[:idx]
   231  		}
   232  
   233  		// Look for foo-bar-baz. The plugin name is "baz"
   234  		parts := strings.SplitN(file, "-", 3)
   235  		if len(parts) != 3 {
   236  			continue
   237  		}
   238  
   239  		log.Printf("[DEBUG] Discovered plugin: %s = %s", parts[2], match)
   240  		(*m)[parts[2]] = match
   241  	}
   242  
   243  	return nil
   244  }
   245  
   246  // ProviderFactories returns the mapping of prefixes to
   247  // ResourceProviderFactory that can be used to instantiate a
   248  // binary-based plugin.
   249  func (c *Config) ProviderFactories() map[string]terraform.ResourceProviderFactory {
   250  	result := make(map[string]terraform.ResourceProviderFactory)
   251  	for k, v := range c.Providers {
   252  		result[k] = c.providerFactory(v)
   253  	}
   254  
   255  	return result
   256  }
   257  
   258  func (c *Config) providerFactory(path string) terraform.ResourceProviderFactory {
   259  	// Build the plugin client configuration and init the plugin
   260  	var config plugin.ClientConfig
   261  	config.Cmd = pluginCmd(path)
   262  	config.HandshakeConfig = tfplugin.Handshake
   263  	config.Managed = true
   264  	config.Plugins = tfplugin.PluginMap
   265  	client := plugin.NewClient(&config)
   266  
   267  	return func() (terraform.ResourceProvider, error) {
   268  		// Request the RPC client so we can get the provider
   269  		// so we can build the actual RPC-implemented provider.
   270  		rpcClient, err := client.Client()
   271  		if err != nil {
   272  			return nil, err
   273  		}
   274  
   275  		raw, err := rpcClient.Dispense(tfplugin.ProviderPluginName)
   276  		if err != nil {
   277  			return nil, err
   278  		}
   279  
   280  		return raw.(terraform.ResourceProvider), nil
   281  	}
   282  }
   283  
   284  // ProvisionerFactories returns the mapping of prefixes to
   285  // ResourceProvisionerFactory that can be used to instantiate a
   286  // binary-based plugin.
   287  func (c *Config) ProvisionerFactories() map[string]terraform.ResourceProvisionerFactory {
   288  	result := make(map[string]terraform.ResourceProvisionerFactory)
   289  	for k, v := range c.Provisioners {
   290  		result[k] = c.provisionerFactory(v)
   291  	}
   292  
   293  	return result
   294  }
   295  
   296  func (c *Config) provisionerFactory(path string) terraform.ResourceProvisionerFactory {
   297  	// Build the plugin client configuration and init the plugin
   298  	var config plugin.ClientConfig
   299  	config.HandshakeConfig = tfplugin.Handshake
   300  	config.Cmd = pluginCmd(path)
   301  	config.Managed = true
   302  	config.Plugins = tfplugin.PluginMap
   303  	client := plugin.NewClient(&config)
   304  
   305  	return func() (terraform.ResourceProvisioner, error) {
   306  		rpcClient, err := client.Client()
   307  		if err != nil {
   308  			return nil, err
   309  		}
   310  
   311  		raw, err := rpcClient.Dispense(tfplugin.ProvisionerPluginName)
   312  		if err != nil {
   313  			return nil, err
   314  		}
   315  
   316  		return raw.(terraform.ResourceProvisioner), nil
   317  	}
   318  }
   319  
   320  func pluginCmd(path string) *exec.Cmd {
   321  	cmdPath := ""
   322  
   323  	// If the path doesn't contain a separator, look in the same
   324  	// directory as the Terraform executable first.
   325  	if !strings.ContainsRune(path, os.PathSeparator) {
   326  		exePath, err := osext.Executable()
   327  		if err == nil {
   328  			temp := filepath.Join(
   329  				filepath.Dir(exePath),
   330  				filepath.Base(path))
   331  
   332  			if _, err := os.Stat(temp); err == nil {
   333  				cmdPath = temp
   334  			}
   335  		}
   336  
   337  		// If we still haven't found the executable, look for it
   338  		// in the PATH.
   339  		if v, err := exec.LookPath(path); err == nil {
   340  			cmdPath = v
   341  		}
   342  	}
   343  
   344  	// No plugin binary found, so try to use an internal plugin.
   345  	if strings.Contains(path, command.TFSPACE) {
   346  		parts := strings.Split(path, command.TFSPACE)
   347  		return exec.Command(parts[0], parts[1:]...)
   348  	}
   349  
   350  	// If we still don't have a path, then just set it to the original
   351  	// given path.
   352  	if cmdPath == "" {
   353  		cmdPath = path
   354  	}
   355  
   356  	// Build the command to execute the plugin
   357  	return exec.Command(cmdPath)
   358  }