github.com/spirius/terraform@v0.10.0-beta2.0.20170714185654-87b2c0cf8fea/command/meta.go (about)

     1  package command
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"errors"
     7  	"flag"
     8  	"fmt"
     9  	"io"
    10  	"io/ioutil"
    11  	"log"
    12  	"os"
    13  	"path/filepath"
    14  	"sort"
    15  	"strconv"
    16  	"strings"
    17  	"time"
    18  
    19  	"github.com/hashicorp/go-getter"
    20  	"github.com/hashicorp/terraform/backend"
    21  	"github.com/hashicorp/terraform/backend/local"
    22  	"github.com/hashicorp/terraform/config"
    23  	"github.com/hashicorp/terraform/helper/experiment"
    24  	"github.com/hashicorp/terraform/helper/variables"
    25  	"github.com/hashicorp/terraform/helper/wrappedstreams"
    26  	"github.com/hashicorp/terraform/terraform"
    27  	"github.com/mitchellh/cli"
    28  	"github.com/mitchellh/colorstring"
    29  )
    30  
    31  // Meta are the meta-options that are available on all or most commands.
    32  type Meta struct {
    33  	// The exported fields below should be set by anyone using a
    34  	// command with a Meta field. These are expected to be set externally
    35  	// (not from within the command itself).
    36  
    37  	Color            bool             // True if output should be colored
    38  	GlobalPluginDirs []string         // Additional paths to search for plugins
    39  	PluginOverrides  *PluginOverrides // legacy overrides from .terraformrc file
    40  	Ui               cli.Ui           // Ui for output
    41  
    42  	// ExtraHooks are extra hooks to add to the context.
    43  	ExtraHooks []terraform.Hook
    44  
    45  	//----------------------------------------------------------
    46  	// Protected: commands can set these
    47  	//----------------------------------------------------------
    48  
    49  	// Modify the data directory location. This should be accessed through the
    50  	// DataDir method.
    51  	dataDir string
    52  
    53  	// pluginPath is a user defined set of directories to look for plugins.
    54  	// This is set during init with the `-plugin-dir` flag, saved to a file in
    55  	// the data directory.
    56  	// This overrides all other search paths when discovering plugins.
    57  	pluginPath []string
    58  
    59  	ignorePluginChecksum bool
    60  
    61  	// Override certain behavior for tests within this package
    62  	testingOverrides *testingOverrides
    63  
    64  	//----------------------------------------------------------
    65  	// Private: do not set these
    66  	//----------------------------------------------------------
    67  
    68  	// backendState is the currently active backend state
    69  	backendState *terraform.BackendState
    70  
    71  	// Variables for the context (private)
    72  	autoKey       string
    73  	autoVariables map[string]interface{}
    74  	input         bool
    75  	variables     map[string]interface{}
    76  
    77  	// Targets for this context (private)
    78  	targets []string
    79  
    80  	// Internal fields
    81  	color bool
    82  	oldUi cli.Ui
    83  
    84  	// The fields below are expected to be set by the command via
    85  	// command line flags. See the Apply command for an example.
    86  	//
    87  	// statePath is the path to the state file. If this is empty, then
    88  	// no state will be loaded. It is also okay for this to be a path to
    89  	// a file that doesn't exist; it is assumed that this means that there
    90  	// is simply no state.
    91  	//
    92  	// stateOutPath is used to override the output path for the state.
    93  	// If not provided, the StatePath is used causing the old state to
    94  	// be overriden.
    95  	//
    96  	// backupPath is used to backup the state file before writing a modified
    97  	// version. It defaults to stateOutPath + DefaultBackupExtension
    98  	//
    99  	// parallelism is used to control the number of concurrent operations
   100  	// allowed when walking the graph
   101  	//
   102  	// shadow is used to enable/disable the shadow graph
   103  	//
   104  	// provider is to specify specific resource providers
   105  	//
   106  	// stateLock is set to false to disable state locking
   107  	//
   108  	// stateLockTimeout is the optional duration to retry a state locks locks
   109  	// when it is already locked by another process.
   110  	//
   111  	// forceInitCopy suppresses confirmation for copying state data during
   112  	// init.
   113  	//
   114  	// reconfigure forces init to ignore any stored configuration.
   115  	statePath        string
   116  	stateOutPath     string
   117  	backupPath       string
   118  	parallelism      int
   119  	shadow           bool
   120  	provider         string
   121  	stateLock        bool
   122  	stateLockTimeout time.Duration
   123  	forceInitCopy    bool
   124  	reconfigure      bool
   125  
   126  	// errWriter is the write side of a pipe for the FlagSet output. We need to
   127  	// keep track of this to close previous pipes between tests. Normal
   128  	// operation never needs to close this.
   129  	errWriter *io.PipeWriter
   130  	// done chan to wait for the scanner goroutine
   131  	errScannerDone chan struct{}
   132  }
   133  
   134  type PluginOverrides struct {
   135  	Providers    map[string]string
   136  	Provisioners map[string]string
   137  }
   138  
   139  type testingOverrides struct {
   140  	ProviderResolver terraform.ResourceProviderResolver
   141  	Provisioners     map[string]terraform.ResourceProvisionerFactory
   142  }
   143  
   144  // initStatePaths is used to initialize the default values for
   145  // statePath, stateOutPath, and backupPath
   146  func (m *Meta) initStatePaths() {
   147  	if m.statePath == "" {
   148  		m.statePath = DefaultStateFilename
   149  	}
   150  	if m.stateOutPath == "" {
   151  		m.stateOutPath = m.statePath
   152  	}
   153  	if m.backupPath == "" {
   154  		m.backupPath = m.stateOutPath + DefaultBackupExtension
   155  	}
   156  }
   157  
   158  // StateOutPath returns the true output path for the state file
   159  func (m *Meta) StateOutPath() string {
   160  	return m.stateOutPath
   161  }
   162  
   163  // Colorize returns the colorization structure for a command.
   164  func (m *Meta) Colorize() *colorstring.Colorize {
   165  	return &colorstring.Colorize{
   166  		Colors:  colorstring.DefaultColors,
   167  		Disable: !m.color,
   168  		Reset:   true,
   169  	}
   170  }
   171  
   172  // DataDir returns the directory where local data will be stored.
   173  // Defaults to DefaultsDataDir in the current working directory.
   174  func (m *Meta) DataDir() string {
   175  	dataDir := DefaultDataDir
   176  	if m.dataDir != "" {
   177  		dataDir = m.dataDir
   178  	}
   179  
   180  	return dataDir
   181  }
   182  
   183  const (
   184  	// InputModeEnvVar is the environment variable that, if set to "false" or
   185  	// "0", causes terraform commands to behave as if the `-input=false` flag was
   186  	// specified.
   187  	InputModeEnvVar = "TF_INPUT"
   188  )
   189  
   190  // InputMode returns the type of input we should ask for in the form of
   191  // terraform.InputMode which is passed directly to Context.Input.
   192  func (m *Meta) InputMode() terraform.InputMode {
   193  	if test || !m.input {
   194  		return 0
   195  	}
   196  
   197  	if envVar := os.Getenv(InputModeEnvVar); envVar != "" {
   198  		if v, err := strconv.ParseBool(envVar); err == nil {
   199  			if !v {
   200  				return 0
   201  			}
   202  		}
   203  	}
   204  
   205  	var mode terraform.InputMode
   206  	mode |= terraform.InputModeProvider
   207  	mode |= terraform.InputModeVar
   208  	mode |= terraform.InputModeVarUnset
   209  
   210  	return mode
   211  }
   212  
   213  // UIInput returns a UIInput object to be used for asking for input.
   214  func (m *Meta) UIInput() terraform.UIInput {
   215  	return &UIInput{
   216  		Colorize: m.Colorize(),
   217  	}
   218  }
   219  
   220  // StdinPiped returns true if the input is piped.
   221  func (m *Meta) StdinPiped() bool {
   222  	fi, err := wrappedstreams.Stdin().Stat()
   223  	if err != nil {
   224  		// If there is an error, let's just say its not piped
   225  		return false
   226  	}
   227  
   228  	return fi.Mode()&os.ModeNamedPipe != 0
   229  }
   230  
   231  const (
   232  	ProviderSkipVerifyEnvVar = "TF_SKIP_PROVIDER_VERIFY"
   233  )
   234  
   235  // contextOpts returns the options to use to initialize a Terraform
   236  // context with the settings from this Meta.
   237  func (m *Meta) contextOpts() *terraform.ContextOpts {
   238  	var opts terraform.ContextOpts
   239  	opts.Hooks = []terraform.Hook{m.uiHook(), &terraform.DebugHook{}}
   240  	opts.Hooks = append(opts.Hooks, m.ExtraHooks...)
   241  
   242  	vs := make(map[string]interface{})
   243  	for k, v := range opts.Variables {
   244  		vs[k] = v
   245  	}
   246  	for k, v := range m.autoVariables {
   247  		vs[k] = v
   248  	}
   249  	for k, v := range m.variables {
   250  		vs[k] = v
   251  	}
   252  	opts.Variables = vs
   253  
   254  	opts.Targets = m.targets
   255  	opts.UIInput = m.UIInput()
   256  	opts.Parallelism = m.parallelism
   257  	opts.Shadow = m.shadow
   258  
   259  	// If testingOverrides are set, we'll skip the plugin discovery process
   260  	// and just work with what we've been given, thus allowing the tests
   261  	// to provide mock providers and provisioners.
   262  	if m.testingOverrides != nil {
   263  		opts.ProviderResolver = m.testingOverrides.ProviderResolver
   264  		opts.Provisioners = m.testingOverrides.Provisioners
   265  	} else {
   266  		opts.ProviderResolver = m.providerResolver()
   267  		opts.Provisioners = m.provisionerFactories()
   268  	}
   269  
   270  	opts.ProviderSHA256s = m.providerPluginsLock().Read()
   271  	if v := os.Getenv(ProviderSkipVerifyEnvVar); v != "" {
   272  		opts.SkipProviderVerify = true
   273  	}
   274  
   275  	opts.Meta = &terraform.ContextMeta{
   276  		Env: m.Workspace(),
   277  	}
   278  
   279  	return &opts
   280  }
   281  
   282  // flags adds the meta flags to the given FlagSet.
   283  func (m *Meta) flagSet(n string) *flag.FlagSet {
   284  	f := flag.NewFlagSet(n, flag.ContinueOnError)
   285  	f.BoolVar(&m.input, "input", true, "input")
   286  	f.Var((*variables.Flag)(&m.variables), "var", "variables")
   287  	f.Var((*variables.FlagFile)(&m.variables), "var-file", "variable file")
   288  	f.Var((*FlagStringSlice)(&m.targets), "target", "resource to target")
   289  
   290  	if m.autoKey != "" {
   291  		f.Var((*variables.FlagFile)(&m.autoVariables), m.autoKey, "variable file")
   292  	}
   293  
   294  	// Advanced (don't need documentation, or unlikely to be set)
   295  	f.BoolVar(&m.shadow, "shadow", true, "shadow graph")
   296  
   297  	// Experimental features
   298  	experiment.Flag(f)
   299  
   300  	// Create an io.Writer that writes to our Ui properly for errors.
   301  	// This is kind of a hack, but it does the job. Basically: create
   302  	// a pipe, use a scanner to break it into lines, and output each line
   303  	// to the UI. Do this forever.
   304  
   305  	// If a previous pipe exists, we need to close that first.
   306  	// This should only happen in testing.
   307  	if m.errWriter != nil {
   308  		m.errWriter.Close()
   309  	}
   310  
   311  	if m.errScannerDone != nil {
   312  		<-m.errScannerDone
   313  	}
   314  
   315  	errR, errW := io.Pipe()
   316  	errScanner := bufio.NewScanner(errR)
   317  	m.errWriter = errW
   318  	m.errScannerDone = make(chan struct{})
   319  	go func() {
   320  		defer close(m.errScannerDone)
   321  		for errScanner.Scan() {
   322  			m.Ui.Error(errScanner.Text())
   323  		}
   324  	}()
   325  	f.SetOutput(errW)
   326  
   327  	// Set the default Usage to empty
   328  	f.Usage = func() {}
   329  
   330  	// command that bypass locking will supply their own flag on this var, but
   331  	// set the initial meta value to true as a failsafe.
   332  	m.stateLock = true
   333  
   334  	return f
   335  }
   336  
   337  // moduleStorage returns the module.Storage implementation used to store
   338  // modules for commands.
   339  func (m *Meta) moduleStorage(root string) getter.Storage {
   340  	return &uiModuleStorage{
   341  		Storage: &getter.FolderStorage{
   342  			StorageDir: filepath.Join(root, "modules"),
   343  		},
   344  		Ui: m.Ui,
   345  	}
   346  }
   347  
   348  // process will process the meta-parameters out of the arguments. This
   349  // will potentially modify the args in-place. It will return the resulting
   350  // slice.
   351  //
   352  // vars says whether or not we support variables.
   353  func (m *Meta) process(args []string, vars bool) ([]string, error) {
   354  	// We do this so that we retain the ability to technically call
   355  	// process multiple times, even if we have no plans to do so
   356  	if m.oldUi != nil {
   357  		m.Ui = m.oldUi
   358  	}
   359  
   360  	// Set colorization
   361  	m.color = m.Color
   362  	for i, v := range args {
   363  		if v == "-no-color" {
   364  			m.color = false
   365  			m.Color = false
   366  			args = append(args[:i], args[i+1:]...)
   367  			break
   368  		}
   369  	}
   370  
   371  	// Set the UI
   372  	m.oldUi = m.Ui
   373  	m.Ui = &cli.ConcurrentUi{
   374  		Ui: &ColorizeUi{
   375  			Colorize:   m.Colorize(),
   376  			ErrorColor: "[red]",
   377  			WarnColor:  "[yellow]",
   378  			Ui:         m.oldUi,
   379  		},
   380  	}
   381  
   382  	// If we support vars and the default var file exists, add it to
   383  	// the args...
   384  	m.autoKey = ""
   385  	if vars {
   386  		var preArgs []string
   387  
   388  		if _, err := os.Stat(DefaultVarsFilename); err == nil {
   389  			m.autoKey = "var-file-default"
   390  			preArgs = append(preArgs, "-"+m.autoKey, DefaultVarsFilename)
   391  		}
   392  
   393  		if _, err := os.Stat(DefaultVarsFilename + ".json"); err == nil {
   394  			m.autoKey = "var-file-default"
   395  			preArgs = append(preArgs, "-"+m.autoKey, DefaultVarsFilename+".json")
   396  		}
   397  
   398  		wd, err := os.Getwd()
   399  		if err != nil {
   400  			return nil, err
   401  		}
   402  
   403  		fis, err := ioutil.ReadDir(wd)
   404  		if err != nil {
   405  			return nil, err
   406  		}
   407  
   408  		// make sure we add the files in order
   409  		sort.Slice(fis, func(i, j int) bool {
   410  			return fis[i].Name() < fis[j].Name()
   411  		})
   412  
   413  		for _, fi := range fis {
   414  			name := fi.Name()
   415  			// Ignore directories, non-var-files, and ignored files
   416  			if fi.IsDir() || !isAutoVarFile(name) || config.IsIgnoredFile(name) {
   417  				continue
   418  			}
   419  
   420  			m.autoKey = "var-file-default"
   421  			preArgs = append(preArgs, "-"+m.autoKey, name)
   422  		}
   423  
   424  		args = append(preArgs, args...)
   425  	}
   426  
   427  	return args, nil
   428  }
   429  
   430  // uiHook returns the UiHook to use with the context.
   431  func (m *Meta) uiHook() *UiHook {
   432  	return &UiHook{
   433  		Colorize: m.Colorize(),
   434  		Ui:       m.Ui,
   435  	}
   436  }
   437  
   438  // confirm asks a yes/no confirmation.
   439  func (m *Meta) confirm(opts *terraform.InputOpts) (bool, error) {
   440  	if !m.Input() {
   441  		return false, errors.New("input is disabled")
   442  	}
   443  	for {
   444  		v, err := m.UIInput().Input(opts)
   445  		if err != nil {
   446  			return false, fmt.Errorf(
   447  				"Error asking for confirmation: %s", err)
   448  		}
   449  
   450  		switch strings.ToLower(v) {
   451  		case "no":
   452  			return false, nil
   453  		case "yes":
   454  			return true, nil
   455  		}
   456  	}
   457  }
   458  
   459  const (
   460  	// ModuleDepthDefault is the default value for
   461  	// module depth, which can be overridden by flag
   462  	// or env var
   463  	ModuleDepthDefault = -1
   464  
   465  	// ModuleDepthEnvVar is the name of the environment variable that can be used to set module depth.
   466  	ModuleDepthEnvVar = "TF_MODULE_DEPTH"
   467  )
   468  
   469  func (m *Meta) addModuleDepthFlag(flags *flag.FlagSet, moduleDepth *int) {
   470  	flags.IntVar(moduleDepth, "module-depth", ModuleDepthDefault, "module-depth")
   471  	if envVar := os.Getenv(ModuleDepthEnvVar); envVar != "" {
   472  		if md, err := strconv.Atoi(envVar); err == nil {
   473  			*moduleDepth = md
   474  		}
   475  	}
   476  }
   477  
   478  // outputShadowError outputs the error from ctx.ShadowError. If the
   479  // error is nil then nothing happens. If output is false then it isn't
   480  // outputted to the user (you can define logic to guard against outputting).
   481  func (m *Meta) outputShadowError(err error, output bool) bool {
   482  	// Do nothing if no error
   483  	if err == nil {
   484  		return false
   485  	}
   486  
   487  	// If not outputting, do nothing
   488  	if !output {
   489  		return false
   490  	}
   491  
   492  	// Write the shadow error output to a file
   493  	path := fmt.Sprintf("terraform-error-%d.log", time.Now().UTC().Unix())
   494  	if err := ioutil.WriteFile(path, []byte(err.Error()), 0644); err != nil {
   495  		// If there is an error writing it, just let it go
   496  		log.Printf("[ERROR] Error writing shadow error: %s", err)
   497  		return false
   498  	}
   499  
   500  	// Output!
   501  	m.Ui.Output(m.Colorize().Color(fmt.Sprintf(
   502  		"[reset][bold][yellow]\nExperimental feature failure! Please report a bug.\n\n"+
   503  			"This is not an error. Your Terraform operation completed successfully.\n"+
   504  			"Your real infrastructure is unaffected by this message.\n\n"+
   505  			"[reset][yellow]While running, Terraform sometimes tests experimental features in the\n"+
   506  			"background. These features cannot affect real state and never touch\n"+
   507  			"real infrastructure. If the features work properly, you see nothing.\n"+
   508  			"If the features fail, this message appears.\n\n"+
   509  			"You can report an issue at: https://github.com/hashicorp/terraform/issues\n\n"+
   510  			"The failure was written to %q. Please\n"+
   511  			"double check this file contains no sensitive information and report\n"+
   512  			"it with your issue.\n\n"+
   513  			"This is not an error. Your terraform operation completed successfully\n"+
   514  			"and your real infrastructure is unaffected by this message.",
   515  		path,
   516  	)))
   517  
   518  	return true
   519  }
   520  
   521  // WorkspaceNameEnvVar is the name of the environment variable that can be used
   522  // to set the name of the Terraform workspace, overriding the workspace chosen
   523  // by `terraform workspace select`.
   524  //
   525  // Note that this environment variable is ignored by `terraform workspace new`
   526  // and `terraform workspace delete`.
   527  const WorkspaceNameEnvVar = "TF_WORKSPACE"
   528  
   529  // Workspace returns the name of the currently configured workspace, corresponding
   530  // to the desired named state.
   531  func (m *Meta) Workspace() string {
   532  	current, _ := m.WorkspaceOverridden()
   533  	return current
   534  }
   535  
   536  // WorkspaceOverridden returns the name of the currently configured workspace,
   537  // corresponding to the desired named state, as well as a bool saying whether
   538  // this was set via the TF_WORKSPACE environment variable.
   539  func (m *Meta) WorkspaceOverridden() (string, bool) {
   540  	if envVar := os.Getenv(WorkspaceNameEnvVar); envVar != "" {
   541  		return envVar, true
   542  	}
   543  
   544  	envData, err := ioutil.ReadFile(filepath.Join(m.DataDir(), local.DefaultWorkspaceFile))
   545  	current := string(bytes.TrimSpace(envData))
   546  	if current == "" {
   547  		current = backend.DefaultStateName
   548  	}
   549  
   550  	if err != nil && !os.IsNotExist(err) {
   551  		// always return the default if we can't get a workspace name
   552  		log.Printf("[ERROR] failed to read current workspace: %s", err)
   553  	}
   554  
   555  	return current, false
   556  }
   557  
   558  // SetWorkspace saves the given name as the current workspace in the local
   559  // filesystem.
   560  func (m *Meta) SetWorkspace(name string) error {
   561  	err := os.MkdirAll(m.DataDir(), 0755)
   562  	if err != nil {
   563  		return err
   564  	}
   565  
   566  	err = ioutil.WriteFile(filepath.Join(m.DataDir(), local.DefaultWorkspaceFile), []byte(name), 0644)
   567  	if err != nil {
   568  		return err
   569  	}
   570  	return nil
   571  }
   572  
   573  // isAutoVarFile determines if the file ends with .auto.tfvars or .auto.tfvars.json
   574  func isAutoVarFile(path string) bool {
   575  	return strings.HasSuffix(path, ".auto.tfvars") ||
   576  		strings.HasSuffix(path, ".auto.tfvars.json")
   577  }