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