github.com/gmlexx/terraform@v0.9.6-0.20170514090029-1532b2a67680/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  	// Replace all env vars
    78  	for k, v := range result.Providers {
    79  		result.Providers[k] = os.ExpandEnv(v)
    80  	}
    81  	for k, v := range result.Provisioners {
    82  		result.Provisioners[k] = os.ExpandEnv(v)
    83  	}
    84  
    85  	return &result, nil
    86  }
    87  
    88  // Discover plugins located on disk, and fall back on plugins baked into the
    89  // Terraform binary.
    90  //
    91  // We look in the following places for plugins:
    92  //
    93  // 1. Terraform configuration path
    94  // 2. Path where Terraform is installed
    95  // 3. Path where Terraform is invoked
    96  //
    97  // Whichever file is discoverd LAST wins.
    98  //
    99  // Finally, we look at the list of plugins compiled into Terraform. If any of
   100  // them has not been found on disk we use the internal version. This allows
   101  // users to add / replace plugins without recompiling the main binary.
   102  func (c *Config) Discover(ui cli.Ui) error {
   103  	// Look in ~/.terraform.d/plugins/
   104  	dir, err := ConfigDir()
   105  	if err != nil {
   106  		log.Printf("[ERR] Error loading config directory: %s", err)
   107  	} else {
   108  		if err := c.discover(filepath.Join(dir, "plugins")); err != nil {
   109  			return err
   110  		}
   111  	}
   112  
   113  	// Next, look in the same directory as the Terraform executable, usually
   114  	// /usr/local/bin. If found, this replaces what we found in the config path.
   115  	exePath, err := osext.Executable()
   116  	if err != nil {
   117  		log.Printf("[ERR] Error loading exe directory: %s", err)
   118  	} else {
   119  		if err := c.discover(filepath.Dir(exePath)); err != nil {
   120  			return err
   121  		}
   122  	}
   123  
   124  	// Finally look in the cwd (where we are invoke Terraform). If found, this
   125  	// replaces anything we found in the config / install paths.
   126  	if err := c.discover("."); err != nil {
   127  		return err
   128  	}
   129  
   130  	// Finally, if we have a plugin compiled into Terraform and we didn't find
   131  	// a replacement on disk, we'll just use the internal version. Only do this
   132  	// from the main process, or the log output will break the plugin handshake.
   133  	if os.Getenv("TF_PLUGIN_MAGIC_COOKIE") == "" {
   134  		for name, _ := range command.InternalProviders {
   135  			if path, found := c.Providers[name]; found {
   136  				// Allow these warnings to be suppressed via TF_PLUGIN_DEV=1 or similar
   137  				if os.Getenv("TF_PLUGIN_DEV") == "" {
   138  					ui.Warn(fmt.Sprintf("[WARN] %s overrides an internal plugin for %s-provider.\n"+
   139  						"  If you did not expect to see this message you will need to remove the old plugin.\n"+
   140  						"  See https://www.terraform.io/docs/internals/internal-plugins.html", path, name))
   141  				}
   142  			} else {
   143  				cmd, err := command.BuildPluginCommandString("provider", name)
   144  				if err != nil {
   145  					return err
   146  				}
   147  				c.Providers[name] = cmd
   148  			}
   149  		}
   150  		for name, _ := range command.InternalProvisioners {
   151  			if path, found := c.Provisioners[name]; found {
   152  				if os.Getenv("TF_PLUGIN_DEV") == "" {
   153  					ui.Warn(fmt.Sprintf("[WARN] %s overrides an internal plugin for %s-provisioner.\n"+
   154  						"  If you did not expect to see this message you will need to remove the old plugin.\n"+
   155  						"  See https://www.terraform.io/docs/internals/internal-plugins.html", path, name))
   156  				}
   157  			} else {
   158  				cmd, err := command.BuildPluginCommandString("provisioner", name)
   159  				if err != nil {
   160  					return err
   161  				}
   162  				c.Provisioners[name] = cmd
   163  			}
   164  		}
   165  	}
   166  
   167  	return nil
   168  }
   169  
   170  // Merge merges two configurations and returns a third entirely
   171  // new configuration with the two merged.
   172  func (c1 *Config) Merge(c2 *Config) *Config {
   173  	var result Config
   174  	result.Providers = make(map[string]string)
   175  	result.Provisioners = make(map[string]string)
   176  	for k, v := range c1.Providers {
   177  		result.Providers[k] = v
   178  	}
   179  	for k, v := range c2.Providers {
   180  		if v1, ok := c1.Providers[k]; ok {
   181  			log.Printf("[INFO] Local %s provider configuration '%s' overrides '%s'", k, v, v1)
   182  		}
   183  		result.Providers[k] = v
   184  	}
   185  	for k, v := range c1.Provisioners {
   186  		result.Provisioners[k] = v
   187  	}
   188  	for k, v := range c2.Provisioners {
   189  		if v1, ok := c1.Provisioners[k]; ok {
   190  			log.Printf("[INFO] Local %s provisioner configuration '%s' overrides '%s'", k, v, v1)
   191  		}
   192  		result.Provisioners[k] = v
   193  	}
   194  	result.DisableCheckpoint = c1.DisableCheckpoint || c2.DisableCheckpoint
   195  	result.DisableCheckpointSignature = c1.DisableCheckpointSignature || c2.DisableCheckpointSignature
   196  
   197  	return &result
   198  }
   199  
   200  func (c *Config) discover(path string) error {
   201  	var err error
   202  
   203  	if !filepath.IsAbs(path) {
   204  		path, err = filepath.Abs(path)
   205  		if err != nil {
   206  			return err
   207  		}
   208  	}
   209  
   210  	err = c.discoverSingle(
   211  		filepath.Join(path, "terraform-provider-*"), &c.Providers)
   212  	if err != nil {
   213  		return err
   214  	}
   215  
   216  	err = c.discoverSingle(
   217  		filepath.Join(path, "terraform-provisioner-*"), &c.Provisioners)
   218  	if err != nil {
   219  		return err
   220  	}
   221  
   222  	return nil
   223  }
   224  
   225  func (c *Config) discoverSingle(glob string, m *map[string]string) error {
   226  	matches, err := filepath.Glob(glob)
   227  	if err != nil {
   228  		return err
   229  	}
   230  
   231  	if *m == nil {
   232  		*m = make(map[string]string)
   233  	}
   234  
   235  	for _, match := range matches {
   236  		file := filepath.Base(match)
   237  
   238  		// If the filename has a ".", trim up to there
   239  		if idx := strings.Index(file, "."); idx >= 0 {
   240  			file = file[:idx]
   241  		}
   242  
   243  		// Look for foo-bar-baz. The plugin name is "baz"
   244  		parts := strings.SplitN(file, "-", 3)
   245  		if len(parts) != 3 {
   246  			continue
   247  		}
   248  
   249  		log.Printf("[DEBUG] Discovered plugin: %s = %s", parts[2], match)
   250  		(*m)[parts[2]] = match
   251  	}
   252  
   253  	return nil
   254  }
   255  
   256  // ProviderFactories returns the mapping of prefixes to
   257  // ResourceProviderFactory that can be used to instantiate a
   258  // binary-based plugin.
   259  func (c *Config) ProviderFactories() map[string]terraform.ResourceProviderFactory {
   260  	result := make(map[string]terraform.ResourceProviderFactory)
   261  	for k, v := range c.Providers {
   262  		result[k] = c.providerFactory(v)
   263  	}
   264  
   265  	return result
   266  }
   267  
   268  func (c *Config) providerFactory(path string) terraform.ResourceProviderFactory {
   269  	// Build the plugin client configuration and init the plugin
   270  	var config plugin.ClientConfig
   271  	config.Cmd = pluginCmd(path)
   272  	config.HandshakeConfig = tfplugin.Handshake
   273  	config.Managed = true
   274  	config.Plugins = tfplugin.PluginMap
   275  	client := plugin.NewClient(&config)
   276  
   277  	return func() (terraform.ResourceProvider, error) {
   278  		// Request the RPC client so we can get the provider
   279  		// so we can build the actual RPC-implemented provider.
   280  		rpcClient, err := client.Client()
   281  		if err != nil {
   282  			return nil, err
   283  		}
   284  
   285  		raw, err := rpcClient.Dispense(tfplugin.ProviderPluginName)
   286  		if err != nil {
   287  			return nil, err
   288  		}
   289  
   290  		return raw.(terraform.ResourceProvider), nil
   291  	}
   292  }
   293  
   294  // ProvisionerFactories returns the mapping of prefixes to
   295  // ResourceProvisionerFactory that can be used to instantiate a
   296  // binary-based plugin.
   297  func (c *Config) ProvisionerFactories() map[string]terraform.ResourceProvisionerFactory {
   298  	result := make(map[string]terraform.ResourceProvisionerFactory)
   299  	for k, v := range c.Provisioners {
   300  		result[k] = c.provisionerFactory(v)
   301  	}
   302  
   303  	return result
   304  }
   305  
   306  func (c *Config) provisionerFactory(path string) terraform.ResourceProvisionerFactory {
   307  	// Build the plugin client configuration and init the plugin
   308  	var config plugin.ClientConfig
   309  	config.HandshakeConfig = tfplugin.Handshake
   310  	config.Cmd = pluginCmd(path)
   311  	config.Managed = true
   312  	config.Plugins = tfplugin.PluginMap
   313  	client := plugin.NewClient(&config)
   314  
   315  	return func() (terraform.ResourceProvisioner, error) {
   316  		rpcClient, err := client.Client()
   317  		if err != nil {
   318  			return nil, err
   319  		}
   320  
   321  		raw, err := rpcClient.Dispense(tfplugin.ProvisionerPluginName)
   322  		if err != nil {
   323  			return nil, err
   324  		}
   325  
   326  		return raw.(terraform.ResourceProvisioner), nil
   327  	}
   328  }
   329  
   330  func pluginCmd(path string) *exec.Cmd {
   331  	cmdPath := ""
   332  
   333  	// If the path doesn't contain a separator, look in the same
   334  	// directory as the Terraform executable first.
   335  	if !strings.ContainsRune(path, os.PathSeparator) {
   336  		exePath, err := osext.Executable()
   337  		if err == nil {
   338  			temp := filepath.Join(
   339  				filepath.Dir(exePath),
   340  				filepath.Base(path))
   341  
   342  			if _, err := os.Stat(temp); err == nil {
   343  				cmdPath = temp
   344  			}
   345  		}
   346  
   347  		// If we still haven't found the executable, look for it
   348  		// in the PATH.
   349  		if v, err := exec.LookPath(path); err == nil {
   350  			cmdPath = v
   351  		}
   352  	}
   353  
   354  	// No plugin binary found, so try to use an internal plugin.
   355  	if strings.Contains(path, command.TFSPACE) {
   356  		parts := strings.Split(path, command.TFSPACE)
   357  		return exec.Command(parts[0], parts[1:]...)
   358  	}
   359  
   360  	// If we still don't have a path, then just set it to the original
   361  	// given path.
   362  	if cmdPath == "" {
   363  		cmdPath = path
   364  	}
   365  
   366  	// Build the command to execute the plugin
   367  	return exec.Command(cmdPath)
   368  }