github.com/bengesoff/terraform@v0.3.1-0.20141018223233-b25a53629922/command/meta.go (about)

     1  package command
     2  
     3  import (
     4  	"bufio"
     5  	"flag"
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  	"path/filepath"
    10  
    11  	"github.com/hashicorp/terraform/config/module"
    12  	"github.com/hashicorp/terraform/terraform"
    13  	"github.com/mitchellh/cli"
    14  	"github.com/mitchellh/colorstring"
    15  )
    16  
    17  // Meta are the meta-options that are available on all or most commands.
    18  type Meta struct {
    19  	Color       bool
    20  	ContextOpts *terraform.ContextOpts
    21  	Ui          cli.Ui
    22  
    23  	// State read when calling `Context`. This is available after calling
    24  	// `Context`.
    25  	state *terraform.State
    26  
    27  	// This can be set by the command itself to provide extra hooks.
    28  	extraHooks []terraform.Hook
    29  
    30  	// This can be set by tests to change some directories
    31  	dataDir string
    32  
    33  	// Variables for the context (private)
    34  	autoKey       string
    35  	autoVariables map[string]string
    36  	input         bool
    37  	variables     map[string]string
    38  
    39  	color bool
    40  	oldUi cli.Ui
    41  }
    42  
    43  // Colorize returns the colorization structure for a command.
    44  func (m *Meta) Colorize() *colorstring.Colorize {
    45  	return &colorstring.Colorize{
    46  		Colors:  colorstring.DefaultColors,
    47  		Disable: !m.color,
    48  		Reset:   true,
    49  	}
    50  }
    51  
    52  // Context returns a Terraform Context taking into account the context
    53  // options used to initialize this meta configuration.
    54  func (m *Meta) Context(copts contextOpts) (*terraform.Context, bool, error) {
    55  	opts := m.contextOpts()
    56  
    57  	// First try to just read the plan directly from the path given.
    58  	f, err := os.Open(copts.Path)
    59  	if err == nil {
    60  		plan, err := terraform.ReadPlan(f)
    61  		f.Close()
    62  		if err == nil {
    63  			if len(m.variables) > 0 {
    64  				return nil, false, fmt.Errorf(
    65  					"You can't set variables with the '-var' or '-var-file' flag\n" +
    66  						"when you're applying a plan file. The variables used when\n" +
    67  						"the plan was created will be used. If you wish to use different\n" +
    68  						"variable values, create a new plan file.")
    69  			}
    70  
    71  			return plan.Context(opts), true, nil
    72  		}
    73  	}
    74  
    75  	// Load up the state
    76  	var state *terraform.State
    77  	if copts.StatePath != "" {
    78  		f, err := os.Open(copts.StatePath)
    79  		if err != nil && os.IsNotExist(err) {
    80  			// If the state file doesn't exist, it is okay, since it
    81  			// is probably a new infrastructure.
    82  			err = nil
    83  		} else if err == nil {
    84  			state, err = terraform.ReadState(f)
    85  			f.Close()
    86  		}
    87  
    88  		if err != nil {
    89  			return nil, false, fmt.Errorf("Error loading state: %s", err)
    90  		}
    91  	}
    92  
    93  	// Store the loaded state
    94  	m.state = state
    95  
    96  	// Load the root module
    97  	mod, err := module.NewTreeModule("", copts.Path)
    98  	if err != nil {
    99  		return nil, false, fmt.Errorf("Error loading config: %s", err)
   100  	}
   101  
   102  	dataDir := DefaultDataDirectory
   103  	if m.dataDir != "" {
   104  		dataDir = m.dataDir
   105  	}
   106  	err = mod.Load(m.moduleStorage(dataDir), copts.GetMode)
   107  	if err != nil {
   108  		return nil, false, fmt.Errorf("Error downloading modules: %s", err)
   109  	}
   110  
   111  	opts.Module = mod
   112  	opts.State = state
   113  	ctx := terraform.NewContext(opts)
   114  	return ctx, false, nil
   115  }
   116  
   117  // InputMode returns the type of input we should ask for in the form of
   118  // terraform.InputMode which is passed directly to Context.Input.
   119  func (m *Meta) InputMode() terraform.InputMode {
   120  	if test || !m.input {
   121  		return 0
   122  	}
   123  
   124  	var mode terraform.InputMode
   125  	mode |= terraform.InputModeProvider
   126  	if len(m.variables) == 0 && m.autoKey == "" {
   127  		mode |= terraform.InputModeVar
   128  	}
   129  
   130  	return mode
   131  }
   132  
   133  // UIInput returns a UIInput object to be used for asking for input.
   134  func (m *Meta) UIInput() terraform.UIInput {
   135  	return &UIInput{
   136  		Colorize: m.Colorize(),
   137  	}
   138  }
   139  
   140  // contextOpts returns the options to use to initialize a Terraform
   141  // context with the settings from this Meta.
   142  func (m *Meta) contextOpts() *terraform.ContextOpts {
   143  	var opts terraform.ContextOpts = *m.ContextOpts
   144  	opts.Hooks = make(
   145  		[]terraform.Hook,
   146  		len(m.ContextOpts.Hooks)+len(m.extraHooks)+1)
   147  	opts.Hooks[0] = m.uiHook()
   148  	copy(opts.Hooks[1:], m.ContextOpts.Hooks)
   149  	copy(opts.Hooks[len(m.ContextOpts.Hooks)+1:], m.extraHooks)
   150  
   151  	vs := make(map[string]string)
   152  	for k, v := range opts.Variables {
   153  		vs[k] = v
   154  	}
   155  	for k, v := range m.autoVariables {
   156  		vs[k] = v
   157  	}
   158  	for k, v := range m.variables {
   159  		vs[k] = v
   160  	}
   161  	opts.Variables = vs
   162  	opts.UIInput = m.UIInput()
   163  
   164  	return &opts
   165  }
   166  
   167  // flags adds the meta flags to the given FlagSet.
   168  func (m *Meta) flagSet(n string) *flag.FlagSet {
   169  	f := flag.NewFlagSet(n, flag.ContinueOnError)
   170  	f.BoolVar(&m.input, "input", true, "input")
   171  	f.Var((*FlagVar)(&m.variables), "var", "variables")
   172  	f.Var((*FlagVarFile)(&m.variables), "var-file", "variable file")
   173  
   174  	if m.autoKey != "" {
   175  		f.Var((*FlagVarFile)(&m.autoVariables), m.autoKey, "variable file")
   176  	}
   177  
   178  	// Create an io.Writer that writes to our Ui properly for errors.
   179  	// This is kind of a hack, but it does the job. Basically: create
   180  	// a pipe, use a scanner to break it into lines, and output each line
   181  	// to the UI. Do this forever.
   182  	errR, errW := io.Pipe()
   183  	errScanner := bufio.NewScanner(errR)
   184  	go func() {
   185  		for errScanner.Scan() {
   186  			m.Ui.Error(errScanner.Text())
   187  		}
   188  	}()
   189  	f.SetOutput(errW)
   190  
   191  	return f
   192  }
   193  
   194  // moduleStorage returns the module.Storage implementation used to store
   195  // modules for commands.
   196  func (m *Meta) moduleStorage(root string) module.Storage {
   197  	return &uiModuleStorage{
   198  		Storage: &module.FolderStorage{
   199  			StorageDir: filepath.Join(root, "modules"),
   200  		},
   201  		Ui: m.Ui,
   202  	}
   203  }
   204  
   205  // process will process the meta-parameters out of the arguments. This
   206  // will potentially modify the args in-place. It will return the resulting
   207  // slice.
   208  //
   209  // vars says whether or not we support variables.
   210  func (m *Meta) process(args []string, vars bool) []string {
   211  	// We do this so that we retain the ability to technically call
   212  	// process multiple times, even if we have no plans to do so
   213  	if m.oldUi != nil {
   214  		m.Ui = m.oldUi
   215  	}
   216  
   217  	// Set colorization
   218  	m.color = m.Color
   219  	for i, v := range args {
   220  		if v == "-no-color" {
   221  			m.color = false
   222  			args = append(args[:i], args[i+1:]...)
   223  			break
   224  		}
   225  	}
   226  
   227  	// Set the UI
   228  	m.oldUi = m.Ui
   229  	m.Ui = &cli.ConcurrentUi{
   230  		Ui: &ColorizeUi{
   231  			Colorize:   m.Colorize(),
   232  			ErrorColor: "[red]",
   233  			Ui:         m.oldUi,
   234  		},
   235  	}
   236  
   237  	// If we support vars and the default var file exists, add it to
   238  	// the args...
   239  	m.autoKey = ""
   240  	if vars {
   241  		if _, err := os.Stat(DefaultVarsFilename); err == nil {
   242  			m.autoKey = "var-file-default"
   243  			args = append(args, "", "")
   244  			copy(args[2:], args[0:])
   245  			args[0] = "-" + m.autoKey
   246  			args[1] = DefaultVarsFilename
   247  		}
   248  	}
   249  
   250  	return args
   251  }
   252  
   253  // uiHook returns the UiHook to use with the context.
   254  func (m *Meta) uiHook() *UiHook {
   255  	return &UiHook{
   256  		Colorize: m.Colorize(),
   257  		Ui:       m.Ui,
   258  	}
   259  }
   260  
   261  // contextOpts are the options used to load a context from a command.
   262  type contextOpts struct {
   263  	// Path to the directory where the root module is.
   264  	Path string
   265  
   266  	// StatePath is the path to the state file. If this is empty, then
   267  	// no state will be loaded. It is also okay for this to be a path to
   268  	// a file that doesn't exist; it is assumed that this means that there
   269  	// is simply no state.
   270  	StatePath string
   271  
   272  	// GetMode is the module.GetMode to use when loading the module tree.
   273  	GetMode module.GetMode
   274  }