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