github.com/grange74/terraform@v0.7.0-rc3.0.20160722171430-8c8803864753/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  		result.Providers[k] = v
   173  	}
   174  	for k, v := range c1.Provisioners {
   175  		result.Provisioners[k] = v
   176  	}
   177  	for k, v := range c2.Provisioners {
   178  		result.Provisioners[k] = v
   179  	}
   180  
   181  	return &result
   182  }
   183  
   184  func (c *Config) discover(path string) error {
   185  	var err error
   186  
   187  	if !filepath.IsAbs(path) {
   188  		path, err = filepath.Abs(path)
   189  		if err != nil {
   190  			return err
   191  		}
   192  	}
   193  
   194  	err = c.discoverSingle(
   195  		filepath.Join(path, "terraform-provider-*"), &c.Providers)
   196  	if err != nil {
   197  		return err
   198  	}
   199  
   200  	err = c.discoverSingle(
   201  		filepath.Join(path, "terraform-provisioner-*"), &c.Provisioners)
   202  	if err != nil {
   203  		return err
   204  	}
   205  
   206  	return nil
   207  }
   208  
   209  func (c *Config) discoverSingle(glob string, m *map[string]string) error {
   210  	matches, err := filepath.Glob(glob)
   211  	if err != nil {
   212  		return err
   213  	}
   214  
   215  	if *m == nil {
   216  		*m = make(map[string]string)
   217  	}
   218  
   219  	for _, match := range matches {
   220  		file := filepath.Base(match)
   221  
   222  		// If the filename has a ".", trim up to there
   223  		if idx := strings.Index(file, "."); idx >= 0 {
   224  			file = file[:idx]
   225  		}
   226  
   227  		// Look for foo-bar-baz. The plugin name is "baz"
   228  		parts := strings.SplitN(file, "-", 3)
   229  		if len(parts) != 3 {
   230  			continue
   231  		}
   232  
   233  		log.Printf("[DEBUG] Discovered plugin: %s = %s", parts[2], match)
   234  		(*m)[parts[2]] = match
   235  	}
   236  
   237  	return nil
   238  }
   239  
   240  // ProviderFactories returns the mapping of prefixes to
   241  // ResourceProviderFactory that can be used to instantiate a
   242  // binary-based plugin.
   243  func (c *Config) ProviderFactories() map[string]terraform.ResourceProviderFactory {
   244  	result := make(map[string]terraform.ResourceProviderFactory)
   245  	for k, v := range c.Providers {
   246  		result[k] = c.providerFactory(v)
   247  	}
   248  
   249  	return result
   250  }
   251  
   252  func (c *Config) providerFactory(path string) terraform.ResourceProviderFactory {
   253  	// Build the plugin client configuration and init the plugin
   254  	var config plugin.ClientConfig
   255  	config.Cmd = pluginCmd(path)
   256  	config.HandshakeConfig = tfplugin.Handshake
   257  	config.Managed = true
   258  	config.Plugins = tfplugin.PluginMap
   259  	client := plugin.NewClient(&config)
   260  
   261  	return func() (terraform.ResourceProvider, error) {
   262  		// Request the RPC client so we can get the provider
   263  		// so we can build the actual RPC-implemented provider.
   264  		rpcClient, err := client.Client()
   265  		if err != nil {
   266  			return nil, err
   267  		}
   268  
   269  		raw, err := rpcClient.Dispense(tfplugin.ProviderPluginName)
   270  		if err != nil {
   271  			return nil, err
   272  		}
   273  
   274  		return raw.(terraform.ResourceProvider), nil
   275  	}
   276  }
   277  
   278  // ProvisionerFactories returns the mapping of prefixes to
   279  // ResourceProvisionerFactory that can be used to instantiate a
   280  // binary-based plugin.
   281  func (c *Config) ProvisionerFactories() map[string]terraform.ResourceProvisionerFactory {
   282  	result := make(map[string]terraform.ResourceProvisionerFactory)
   283  	for k, v := range c.Provisioners {
   284  		result[k] = c.provisionerFactory(v)
   285  	}
   286  
   287  	return result
   288  }
   289  
   290  func (c *Config) provisionerFactory(path string) terraform.ResourceProvisionerFactory {
   291  	// Build the plugin client configuration and init the plugin
   292  	var config plugin.ClientConfig
   293  	config.HandshakeConfig = tfplugin.Handshake
   294  	config.Cmd = pluginCmd(path)
   295  	config.Managed = true
   296  	config.Plugins = tfplugin.PluginMap
   297  	client := plugin.NewClient(&config)
   298  
   299  	return func() (terraform.ResourceProvisioner, error) {
   300  		rpcClient, err := client.Client()
   301  		if err != nil {
   302  			return nil, err
   303  		}
   304  
   305  		raw, err := rpcClient.Dispense(tfplugin.ProvisionerPluginName)
   306  		if err != nil {
   307  			return nil, err
   308  		}
   309  
   310  		return raw.(terraform.ResourceProvisioner), nil
   311  	}
   312  }
   313  
   314  func pluginCmd(path string) *exec.Cmd {
   315  	cmdPath := ""
   316  
   317  	// If the path doesn't contain a separator, look in the same
   318  	// directory as the Terraform executable first.
   319  	if !strings.ContainsRune(path, os.PathSeparator) {
   320  		exePath, err := osext.Executable()
   321  		if err == nil {
   322  			temp := filepath.Join(
   323  				filepath.Dir(exePath),
   324  				filepath.Base(path))
   325  
   326  			if _, err := os.Stat(temp); err == nil {
   327  				cmdPath = temp
   328  			}
   329  		}
   330  
   331  		// If we still haven't found the executable, look for it
   332  		// in the PATH.
   333  		if v, err := exec.LookPath(path); err == nil {
   334  			cmdPath = v
   335  		}
   336  	}
   337  
   338  	// No plugin binary found, so try to use an internal plugin.
   339  	if strings.Contains(path, command.TFSPACE) {
   340  		parts := strings.Split(path, command.TFSPACE)
   341  		return exec.Command(parts[0], parts[1:]...)
   342  	}
   343  
   344  	// If we still don't have a path, then just set it to the original
   345  	// given path.
   346  	if cmdPath == "" {
   347  		cmdPath = path
   348  	}
   349  
   350  	// Build the command to execute the plugin
   351  	return exec.Command(cmdPath)
   352  }