2014-07-12 22:21:46 -05:00
|
|
|
package command
|
|
|
|
|
|
|
|
import (
|
2014-09-08 22:56:18 -05:00
|
|
|
"bufio"
|
2014-07-18 13:37:27 -05:00
|
|
|
"flag"
|
2014-07-12 22:37:30 -05:00
|
|
|
"fmt"
|
2014-09-08 22:56:18 -05:00
|
|
|
"io"
|
2016-11-02 12:30:28 -05:00
|
|
|
"log"
|
2014-07-12 22:37:30 -05:00
|
|
|
"os"
|
2014-09-22 13:15:27 -05:00
|
|
|
"path/filepath"
|
2015-04-30 09:59:14 -05:00
|
|
|
"strconv"
|
2014-07-12 22:37:30 -05:00
|
|
|
|
2016-11-02 12:30:28 -05:00
|
|
|
"github.com/hashicorp/errwrap"
|
2015-10-15 15:48:58 -05:00
|
|
|
"github.com/hashicorp/go-getter"
|
2016-11-02 12:30:28 -05:00
|
|
|
"github.com/hashicorp/terraform/config"
|
2014-09-22 12:56:50 -05:00
|
|
|
"github.com/hashicorp/terraform/config/module"
|
2016-10-26 14:46:22 -05:00
|
|
|
"github.com/hashicorp/terraform/helper/experiment"
|
2016-11-14 00:18:18 -06:00
|
|
|
"github.com/hashicorp/terraform/helper/wrappedreadline"
|
2015-02-21 18:04:32 -06:00
|
|
|
"github.com/hashicorp/terraform/state"
|
2014-07-12 22:37:30 -05:00
|
|
|
"github.com/hashicorp/terraform/terraform"
|
|
|
|
"github.com/mitchellh/cli"
|
2014-07-12 22:21:46 -05:00
|
|
|
"github.com/mitchellh/colorstring"
|
2016-11-14 00:18:18 -06:00
|
|
|
"github.com/mitchellh/panicwrap"
|
2014-07-12 22:21:46 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
// Meta are the meta-options that are available on all or most commands.
|
|
|
|
type Meta struct {
|
2014-07-12 22:37:30 -05:00
|
|
|
Color bool
|
|
|
|
ContextOpts *terraform.ContextOpts
|
|
|
|
Ui cli.Ui
|
2014-07-28 00:58:35 -05:00
|
|
|
|
|
|
|
// State read when calling `Context`. This is available after calling
|
|
|
|
// `Context`.
|
2015-02-23 12:20:40 -06:00
|
|
|
state state.State
|
|
|
|
stateResult *StateResult
|
2014-07-12 22:59:16 -05:00
|
|
|
|
2014-07-17 17:14:26 -05:00
|
|
|
// This can be set by the command itself to provide extra hooks.
|
|
|
|
extraHooks []terraform.Hook
|
|
|
|
|
2014-10-13 14:05:28 -05:00
|
|
|
// This can be set by tests to change some directories
|
|
|
|
dataDir string
|
|
|
|
|
2014-07-18 13:37:27 -05:00
|
|
|
// Variables for the context (private)
|
2014-08-24 23:40:58 -05:00
|
|
|
autoKey string
|
core: Allow lists and maps as variable overrides
Terraform 0.7 introduces lists and maps as first-class values for
variables, in addition to string values which were previously available.
However, there was previously no way to override the default value of a
list or map, and the functionality for overriding specific map keys was
broken.
Using the environment variable method for setting variable values, there
was previously no way to give a variable a value of a list or map. These
now support HCL for individual values - specifying:
TF_VAR_test='["Hello", "World"]'
will set the variable `test` to a two-element list containing "Hello"
and "World". Specifying
TF_VAR_test_map='{"Hello = "World", "Foo" = "bar"}'
will set the variable `test_map` to a two-element map with keys "Hello"
and "Foo", and values "World" and "bar" respectively.
The same logic is applied to `-var` flags, and the file parsed by
`-var-files` ("autoVariables").
Note that care must be taken to not run into shell expansion for `-var-`
flags and environment variables.
We also merge map keys where appropriate. The override syntax has
changed (to be noted in CHANGELOG as a breaking change), so several
tests needed their syntax updating from the old `amis.us-east-1 =
"newValue"` style to `amis = "{ "us-east-1" = "newValue"}"` style as
defined in TF-002.
In order to continue supporting the `-var "foo=bar"` type of variable
flag (which is not valid HCL), a special case error is checked after HCL
parsing fails, and the old code path runs instead.
2016-07-20 20:38:26 -05:00
|
|
|
autoVariables map[string]interface{}
|
2014-09-29 13:11:35 -05:00
|
|
|
input bool
|
core: Allow lists and maps as variable overrides
Terraform 0.7 introduces lists and maps as first-class values for
variables, in addition to string values which were previously available.
However, there was previously no way to override the default value of a
list or map, and the functionality for overriding specific map keys was
broken.
Using the environment variable method for setting variable values, there
was previously no way to give a variable a value of a list or map. These
now support HCL for individual values - specifying:
TF_VAR_test='["Hello", "World"]'
will set the variable `test` to a two-element list containing "Hello"
and "World". Specifying
TF_VAR_test_map='{"Hello = "World", "Foo" = "bar"}'
will set the variable `test_map` to a two-element map with keys "Hello"
and "Foo", and values "World" and "bar" respectively.
The same logic is applied to `-var` flags, and the file parsed by
`-var-files` ("autoVariables").
Note that care must be taken to not run into shell expansion for `-var-`
flags and environment variables.
We also merge map keys where appropriate. The override syntax has
changed (to be noted in CHANGELOG as a breaking change), so several
tests needed their syntax updating from the old `amis.us-east-1 =
"newValue"` style to `amis = "{ "us-east-1" = "newValue"}"` style as
defined in TF-002.
In order to continue supporting the `-var "foo=bar"` type of variable
flag (which is not valid HCL), a special case error is checked after HCL
parsing fails, and the old code path runs instead.
2016-07-20 20:38:26 -05:00
|
|
|
variables map[string]interface{}
|
2014-07-18 13:37:27 -05:00
|
|
|
|
2015-03-24 11:18:15 -05:00
|
|
|
// Targets for this context (private)
|
|
|
|
targets []string
|
|
|
|
|
2014-07-17 11:34:32 -05:00
|
|
|
color bool
|
2014-07-12 22:59:16 -05:00
|
|
|
oldUi cli.Ui
|
2014-10-11 20:05:23 -05:00
|
|
|
|
2015-02-21 20:17:40 -06:00
|
|
|
// The fields below are expected to be set by the command via
|
|
|
|
// command line flags. See the Apply command for an example.
|
|
|
|
//
|
2014-10-11 20:05:23 -05:00
|
|
|
// statePath is the path to the state file. If this is empty, then
|
|
|
|
// no state will be loaded. It is also okay for this to be a path to
|
|
|
|
// a file that doesn't exist; it is assumed that this means that there
|
|
|
|
// is simply no state.
|
2015-02-21 20:17:40 -06:00
|
|
|
//
|
2014-10-11 20:05:23 -05:00
|
|
|
// stateOutPath is used to override the output path for the state.
|
|
|
|
// If not provided, the StatePath is used causing the old state to
|
|
|
|
// be overriden.
|
2015-02-21 20:17:40 -06:00
|
|
|
//
|
2014-10-11 20:05:23 -05:00
|
|
|
// backupPath is used to backup the state file before writing a modified
|
2015-09-11 13:56:20 -05:00
|
|
|
// version. It defaults to stateOutPath + DefaultBackupExtension
|
2015-05-06 10:58:42 -05:00
|
|
|
//
|
|
|
|
// parallelism is used to control the number of concurrent operations
|
|
|
|
// allowed when walking the graph
|
2016-10-21 16:25:05 -05:00
|
|
|
//
|
|
|
|
// shadow is used to enable/disable the shadow graph
|
2015-02-21 20:17:40 -06:00
|
|
|
statePath string
|
|
|
|
stateOutPath string
|
|
|
|
backupPath string
|
2015-05-06 10:58:42 -05:00
|
|
|
parallelism int
|
2016-10-21 16:25:05 -05:00
|
|
|
shadow bool
|
2014-10-11 20:05:23 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// initStatePaths is used to initialize the default values for
|
|
|
|
// statePath, stateOutPath, and backupPath
|
|
|
|
func (m *Meta) initStatePaths() {
|
|
|
|
if m.statePath == "" {
|
|
|
|
m.statePath = DefaultStateFilename
|
|
|
|
}
|
|
|
|
if m.stateOutPath == "" {
|
|
|
|
m.stateOutPath = m.statePath
|
|
|
|
}
|
|
|
|
if m.backupPath == "" {
|
2015-09-11 13:56:20 -05:00
|
|
|
m.backupPath = m.stateOutPath + DefaultBackupExtension
|
2014-10-11 20:05:23 -05:00
|
|
|
}
|
2014-07-12 22:21:46 -05:00
|
|
|
}
|
|
|
|
|
2014-10-11 20:34:11 -05:00
|
|
|
// StateOutPath returns the true output path for the state file
|
|
|
|
func (m *Meta) StateOutPath() string {
|
|
|
|
return m.stateOutPath
|
|
|
|
}
|
|
|
|
|
2014-07-12 22:21:46 -05:00
|
|
|
// Colorize returns the colorization structure for a command.
|
|
|
|
func (m *Meta) Colorize() *colorstring.Colorize {
|
|
|
|
return &colorstring.Colorize{
|
|
|
|
Colors: colorstring.DefaultColors,
|
2014-07-17 11:34:32 -05:00
|
|
|
Disable: !m.color,
|
2014-07-12 22:21:46 -05:00
|
|
|
Reset: true,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-07-12 22:37:30 -05:00
|
|
|
// Context returns a Terraform Context taking into account the context
|
|
|
|
// options used to initialize this meta configuration.
|
2014-09-22 12:56:50 -05:00
|
|
|
func (m *Meta) Context(copts contextOpts) (*terraform.Context, bool, error) {
|
2014-07-12 22:37:30 -05:00
|
|
|
opts := m.contextOpts()
|
|
|
|
|
|
|
|
// First try to just read the plan directly from the path given.
|
2014-09-22 12:56:50 -05:00
|
|
|
f, err := os.Open(copts.Path)
|
2014-07-12 22:37:30 -05:00
|
|
|
if err == nil {
|
|
|
|
plan, err := terraform.ReadPlan(f)
|
|
|
|
f.Close()
|
|
|
|
if err == nil {
|
2016-10-28 19:51:05 -05:00
|
|
|
// Setup our state, force it to use our plan's state
|
|
|
|
stateOpts := m.StateOpts()
|
|
|
|
if plan != nil {
|
|
|
|
stateOpts.ForceState = plan.State
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get the state
|
|
|
|
result, err := State(stateOpts)
|
2015-02-21 20:00:08 -06:00
|
|
|
if err != nil {
|
|
|
|
return nil, false, fmt.Errorf("Error loading plan: %s", err)
|
2015-01-07 15:08:32 -06:00
|
|
|
}
|
|
|
|
|
2015-02-21 20:00:08 -06:00
|
|
|
// Set our state
|
2016-10-28 19:51:05 -05:00
|
|
|
m.state = result.State
|
2016-06-30 15:50:56 -05:00
|
|
|
|
|
|
|
// this is used for printing the saved location later
|
|
|
|
if m.stateOutPath == "" {
|
2016-10-28 19:51:05 -05:00
|
|
|
m.stateOutPath = result.StatePath
|
2016-06-30 15:50:56 -05:00
|
|
|
}
|
2015-02-21 20:00:08 -06:00
|
|
|
|
2014-07-26 16:32:09 -05:00
|
|
|
if len(m.variables) > 0 {
|
2014-07-26 19:45:38 -05:00
|
|
|
return nil, false, fmt.Errorf(
|
2014-07-26 16:32:09 -05:00
|
|
|
"You can't set variables with the '-var' or '-var-file' flag\n" +
|
|
|
|
"when you're applying a plan file. The variables used when\n" +
|
|
|
|
"the plan was created will be used. If you wish to use different\n" +
|
|
|
|
"variable values, create a new plan file.")
|
|
|
|
}
|
|
|
|
|
2016-03-11 13:07:54 -06:00
|
|
|
ctx, err := plan.Context(opts)
|
|
|
|
return ctx, true, err
|
2014-07-12 22:37:30 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-11 20:21:20 -05:00
|
|
|
// Load the statePath if not given
|
2014-09-22 12:56:50 -05:00
|
|
|
if copts.StatePath != "" {
|
2014-10-11 20:21:20 -05:00
|
|
|
m.statePath = copts.StatePath
|
2014-07-12 22:37:30 -05:00
|
|
|
}
|
|
|
|
|
2015-03-24 11:18:15 -05:00
|
|
|
// Tell the context if we're in a destroy plan / apply
|
|
|
|
opts.Destroy = copts.Destroy
|
|
|
|
|
2014-07-27 17:09:04 -05:00
|
|
|
// Store the loaded state
|
2015-02-21 20:17:40 -06:00
|
|
|
state, err := m.State()
|
2014-10-11 20:21:20 -05:00
|
|
|
if err != nil {
|
|
|
|
return nil, false, err
|
|
|
|
}
|
2014-07-27 17:09:04 -05:00
|
|
|
|
2014-09-22 12:56:50 -05:00
|
|
|
// Load the root module
|
2016-05-04 12:46:34 -05:00
|
|
|
var mod *module.Tree
|
|
|
|
if copts.Path != "" {
|
|
|
|
mod, err = module.NewTreeModule("", copts.Path)
|
2016-11-02 12:30:28 -05:00
|
|
|
|
|
|
|
// Check for the error where we have no config files but
|
|
|
|
// allow that. If that happens, clear the error.
|
|
|
|
if errwrap.ContainsType(err, new(config.ErrNoConfigsFound)) &&
|
|
|
|
copts.PathEmptyOk {
|
|
|
|
log.Printf(
|
|
|
|
"[WARN] Empty configuration dir, ignoring: %s", copts.Path)
|
|
|
|
err = nil
|
|
|
|
mod = module.NewEmptyTree()
|
|
|
|
}
|
|
|
|
|
2016-05-04 12:46:34 -05:00
|
|
|
if err != nil {
|
|
|
|
return nil, false, fmt.Errorf("Error loading config: %s", err)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
mod = module.NewEmptyTree()
|
2014-07-12 22:37:30 -05:00
|
|
|
}
|
2014-10-13 14:05:28 -05:00
|
|
|
|
2015-03-04 22:52:06 -06:00
|
|
|
err = mod.Load(m.moduleStorage(m.DataDir()), copts.GetMode)
|
2014-09-22 13:15:27 -05:00
|
|
|
if err != nil {
|
|
|
|
return nil, false, fmt.Errorf("Error downloading modules: %s", err)
|
|
|
|
}
|
2014-07-12 22:37:30 -05:00
|
|
|
|
2016-09-03 17:26:49 -05:00
|
|
|
// Validate the module right away
|
|
|
|
if err := mod.Validate(); err != nil {
|
|
|
|
return nil, false, err
|
|
|
|
}
|
|
|
|
|
2014-09-24 16:37:24 -05:00
|
|
|
opts.Module = mod
|
2015-05-06 10:58:42 -05:00
|
|
|
opts.Parallelism = copts.Parallelism
|
2015-02-21 18:04:32 -06:00
|
|
|
opts.State = state.State()
|
2016-03-11 13:07:54 -06:00
|
|
|
ctx, err := terraform.NewContext(opts)
|
|
|
|
return ctx, false, err
|
2014-07-12 22:37:30 -05:00
|
|
|
}
|
|
|
|
|
2015-03-04 22:52:06 -06:00
|
|
|
// DataDir returns the directory where local data will be stored.
|
|
|
|
func (m *Meta) DataDir() string {
|
2016-07-20 17:55:05 -05:00
|
|
|
dataDir := DefaultDataDir
|
2015-03-04 22:52:06 -06:00
|
|
|
if m.dataDir != "" {
|
|
|
|
dataDir = m.dataDir
|
|
|
|
}
|
|
|
|
|
|
|
|
return dataDir
|
|
|
|
}
|
|
|
|
|
2015-04-30 09:59:14 -05:00
|
|
|
const (
|
|
|
|
// InputModeEnvVar is the environment variable that, if set to "false" or
|
|
|
|
// "0", causes terraform commands to behave as if the `-input=false` flag was
|
|
|
|
// specified.
|
|
|
|
InputModeEnvVar = "TF_INPUT"
|
|
|
|
)
|
|
|
|
|
2014-10-08 12:29:54 -05:00
|
|
|
// InputMode returns the type of input we should ask for in the form of
|
|
|
|
// terraform.InputMode which is passed directly to Context.Input.
|
|
|
|
func (m *Meta) InputMode() terraform.InputMode {
|
|
|
|
if test || !m.input {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
2015-04-30 09:59:14 -05:00
|
|
|
if envVar := os.Getenv(InputModeEnvVar); envVar != "" {
|
|
|
|
if v, err := strconv.ParseBool(envVar); err == nil {
|
|
|
|
if !v {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-08 12:29:54 -05:00
|
|
|
var mode terraform.InputMode
|
|
|
|
mode |= terraform.InputModeProvider
|
2016-11-01 21:16:43 -05:00
|
|
|
mode |= terraform.InputModeVar
|
|
|
|
mode |= terraform.InputModeVarUnset
|
2014-10-08 12:29:54 -05:00
|
|
|
|
|
|
|
return mode
|
2014-09-29 13:11:35 -05:00
|
|
|
}
|
|
|
|
|
2015-02-21 18:04:32 -06:00
|
|
|
// State returns the state for this meta.
|
|
|
|
func (m *Meta) State() (state.State, error) {
|
|
|
|
if m.state != nil {
|
|
|
|
return m.state, nil
|
2014-10-01 00:01:11 -05:00
|
|
|
}
|
|
|
|
|
2015-02-23 12:20:40 -06:00
|
|
|
result, err := State(m.StateOpts())
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
m.state = result.State
|
|
|
|
m.stateOutPath = result.StatePath
|
|
|
|
m.stateResult = result
|
|
|
|
return m.state, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// StateRaw is used to setup the state manually.
|
|
|
|
func (m *Meta) StateRaw(opts *StateOpts) (*StateResult, error) {
|
|
|
|
result, err := State(opts)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
m.state = result.State
|
|
|
|
m.stateOutPath = result.StatePath
|
|
|
|
m.stateResult = result
|
|
|
|
return result, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// StateOpts returns the default state options
|
|
|
|
func (m *Meta) StateOpts() *StateOpts {
|
2015-02-21 20:30:41 -06:00
|
|
|
localPath := m.statePath
|
|
|
|
if localPath == "" {
|
|
|
|
localPath = DefaultStateFilename
|
2015-02-21 20:20:04 -06:00
|
|
|
}
|
2015-03-04 22:52:06 -06:00
|
|
|
remotePath := filepath.Join(m.DataDir(), DefaultStateFilename)
|
2015-02-21 20:20:04 -06:00
|
|
|
|
2015-02-23 12:20:40 -06:00
|
|
|
return &StateOpts{
|
|
|
|
LocalPath: localPath,
|
|
|
|
LocalPathOut: m.stateOutPath,
|
|
|
|
RemotePath: remotePath,
|
|
|
|
RemoteRefresh: true,
|
|
|
|
BackupPath: m.backupPath,
|
2014-10-11 20:21:20 -05:00
|
|
|
}
|
2015-02-21 18:04:32 -06:00
|
|
|
}
|
2014-10-11 20:21:20 -05:00
|
|
|
|
2015-02-21 18:04:32 -06:00
|
|
|
// UIInput returns a UIInput object to be used for asking for input.
|
|
|
|
func (m *Meta) UIInput() terraform.UIInput {
|
|
|
|
return &UIInput{
|
|
|
|
Colorize: m.Colorize(),
|
2014-10-11 20:21:20 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-10-11 20:05:23 -05:00
|
|
|
// PersistState is used to write out the state, handling backup of
|
|
|
|
// the existing state file and respecting path configurations.
|
|
|
|
func (m *Meta) PersistState(s *terraform.State) error {
|
2015-02-21 18:04:32 -06:00
|
|
|
if err := m.state.WriteState(s); err != nil {
|
2014-10-11 20:05:23 -05:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-02-21 18:04:32 -06:00
|
|
|
return m.state.PersistState()
|
2014-10-11 20:05:23 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// Input returns true if we should ask for input for context.
|
|
|
|
func (m *Meta) Input() bool {
|
|
|
|
return !test && m.input && len(m.variables) == 0
|
|
|
|
}
|
|
|
|
|
2016-11-14 00:18:18 -06:00
|
|
|
// StdinPiped returns true if the input is piped.
|
|
|
|
func (m *Meta) StdinPiped() bool {
|
|
|
|
fi, err := m.Stdin().Stat()
|
|
|
|
if err != nil {
|
|
|
|
// If there is an error, let's just say its not piped
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return fi.Mode()&os.ModeNamedPipe != 0
|
|
|
|
}
|
|
|
|
|
|
|
|
// Stdin returns the stdin for this command.
|
|
|
|
func (m *Meta) Stdin() *os.File {
|
|
|
|
stdin := os.Stdin
|
|
|
|
if panicwrap.Wrapped(nil) {
|
|
|
|
stdin = wrappedreadline.Stdin
|
|
|
|
}
|
|
|
|
|
|
|
|
return stdin
|
|
|
|
}
|
|
|
|
|
|
|
|
// Stdout returns the stdout for this command.
|
|
|
|
func (m *Meta) Stdout() *os.File {
|
|
|
|
stdout := os.Stdout
|
|
|
|
if panicwrap.Wrapped(nil) {
|
|
|
|
stdout = wrappedreadline.Stdout
|
|
|
|
}
|
|
|
|
|
|
|
|
return stdout
|
|
|
|
}
|
|
|
|
|
|
|
|
// Stderr returns the stderr for this command.
|
|
|
|
func (m *Meta) Stderr() *os.File {
|
|
|
|
stderr := os.Stderr
|
|
|
|
if panicwrap.Wrapped(nil) {
|
|
|
|
stderr = wrappedreadline.Stderr
|
|
|
|
}
|
|
|
|
|
|
|
|
return stderr
|
|
|
|
}
|
|
|
|
|
2014-07-12 22:37:30 -05:00
|
|
|
// contextOpts returns the options to use to initialize a Terraform
|
|
|
|
// context with the settings from this Meta.
|
|
|
|
func (m *Meta) contextOpts() *terraform.ContextOpts {
|
|
|
|
var opts terraform.ContextOpts = *m.ContextOpts
|
2016-11-04 10:30:51 -05:00
|
|
|
|
|
|
|
opts.Hooks = []terraform.Hook{m.uiHook(), &terraform.DebugHook{}}
|
|
|
|
opts.Hooks = append(opts.Hooks, m.ContextOpts.Hooks...)
|
|
|
|
opts.Hooks = append(opts.Hooks, m.extraHooks...)
|
2014-07-18 13:37:27 -05:00
|
|
|
|
2016-07-18 12:52:10 -05:00
|
|
|
vs := make(map[string]interface{})
|
2014-08-24 23:40:58 -05:00
|
|
|
for k, v := range opts.Variables {
|
|
|
|
vs[k] = v
|
|
|
|
}
|
|
|
|
for k, v := range m.autoVariables {
|
|
|
|
vs[k] = v
|
|
|
|
}
|
|
|
|
for k, v := range m.variables {
|
|
|
|
vs[k] = v
|
2014-07-18 13:37:27 -05:00
|
|
|
}
|
2014-08-24 23:40:58 -05:00
|
|
|
opts.Variables = vs
|
2015-03-24 11:18:15 -05:00
|
|
|
opts.Targets = m.targets
|
2014-10-01 00:01:11 -05:00
|
|
|
opts.UIInput = m.UIInput()
|
2016-10-21 16:25:05 -05:00
|
|
|
opts.Shadow = m.shadow
|
2014-07-18 13:37:27 -05:00
|
|
|
|
2014-07-12 22:37:30 -05:00
|
|
|
return &opts
|
|
|
|
}
|
|
|
|
|
2014-07-18 13:37:27 -05:00
|
|
|
// flags adds the meta flags to the given FlagSet.
|
|
|
|
func (m *Meta) flagSet(n string) *flag.FlagSet {
|
|
|
|
f := flag.NewFlagSet(n, flag.ContinueOnError)
|
2014-09-29 13:11:35 -05:00
|
|
|
f.BoolVar(&m.input, "input", true, "input")
|
core: Allow lists and maps as variable overrides
Terraform 0.7 introduces lists and maps as first-class values for
variables, in addition to string values which were previously available.
However, there was previously no way to override the default value of a
list or map, and the functionality for overriding specific map keys was
broken.
Using the environment variable method for setting variable values, there
was previously no way to give a variable a value of a list or map. These
now support HCL for individual values - specifying:
TF_VAR_test='["Hello", "World"]'
will set the variable `test` to a two-element list containing "Hello"
and "World". Specifying
TF_VAR_test_map='{"Hello = "World", "Foo" = "bar"}'
will set the variable `test_map` to a two-element map with keys "Hello"
and "Foo", and values "World" and "bar" respectively.
The same logic is applied to `-var` flags, and the file parsed by
`-var-files` ("autoVariables").
Note that care must be taken to not run into shell expansion for `-var-`
flags and environment variables.
We also merge map keys where appropriate. The override syntax has
changed (to be noted in CHANGELOG as a breaking change), so several
tests needed their syntax updating from the old `amis.us-east-1 =
"newValue"` style to `amis = "{ "us-east-1" = "newValue"}"` style as
defined in TF-002.
In order to continue supporting the `-var "foo=bar"` type of variable
flag (which is not valid HCL), a special case error is checked after HCL
parsing fails, and the old code path runs instead.
2016-07-20 20:38:26 -05:00
|
|
|
f.Var((*FlagTypedKV)(&m.variables), "var", "variables")
|
2015-02-21 20:30:41 -06:00
|
|
|
f.Var((*FlagKVFile)(&m.variables), "var-file", "variable file")
|
2015-03-24 11:18:15 -05:00
|
|
|
f.Var((*FlagStringSlice)(&m.targets), "target", "resource to target")
|
2014-08-24 23:40:58 -05:00
|
|
|
|
|
|
|
if m.autoKey != "" {
|
2015-02-21 20:30:41 -06:00
|
|
|
f.Var((*FlagKVFile)(&m.autoVariables), m.autoKey, "variable file")
|
2014-08-24 23:40:58 -05:00
|
|
|
}
|
|
|
|
|
2016-10-21 16:25:05 -05:00
|
|
|
// Advanced (don't need documentation, or unlikely to be set)
|
|
|
|
f.BoolVar(&m.shadow, "shadow", true, "shadow graph")
|
|
|
|
|
2016-10-15 17:08:11 -05:00
|
|
|
// Experimental features
|
2016-10-26 14:46:22 -05:00
|
|
|
experiment.Flag(f)
|
2016-10-15 17:08:11 -05:00
|
|
|
|
2014-09-08 22:56:18 -05:00
|
|
|
// Create an io.Writer that writes to our Ui properly for errors.
|
|
|
|
// This is kind of a hack, but it does the job. Basically: create
|
|
|
|
// a pipe, use a scanner to break it into lines, and output each line
|
|
|
|
// to the UI. Do this forever.
|
|
|
|
errR, errW := io.Pipe()
|
|
|
|
errScanner := bufio.NewScanner(errR)
|
|
|
|
go func() {
|
|
|
|
for errScanner.Scan() {
|
|
|
|
m.Ui.Error(errScanner.Text())
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
f.SetOutput(errW)
|
|
|
|
|
2016-03-22 12:41:02 -05:00
|
|
|
// Set the default Usage to empty
|
|
|
|
f.Usage = func() {}
|
|
|
|
|
2014-07-18 13:37:27 -05:00
|
|
|
return f
|
|
|
|
}
|
|
|
|
|
2014-09-22 13:15:27 -05:00
|
|
|
// moduleStorage returns the module.Storage implementation used to store
|
|
|
|
// modules for commands.
|
2015-10-15 15:48:58 -05:00
|
|
|
func (m *Meta) moduleStorage(root string) getter.Storage {
|
2014-09-22 13:15:27 -05:00
|
|
|
return &uiModuleStorage{
|
2015-10-15 15:48:58 -05:00
|
|
|
Storage: &getter.FolderStorage{
|
2014-09-22 13:15:27 -05:00
|
|
|
StorageDir: filepath.Join(root, "modules"),
|
|
|
|
},
|
|
|
|
Ui: m.Ui,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-07-12 22:21:46 -05:00
|
|
|
// process will process the meta-parameters out of the arguments. This
|
|
|
|
// will potentially modify the args in-place. It will return the resulting
|
|
|
|
// slice.
|
2014-10-01 10:37:57 -05:00
|
|
|
//
|
|
|
|
// vars says whether or not we support variables.
|
2014-08-05 11:32:01 -05:00
|
|
|
func (m *Meta) process(args []string, vars bool) []string {
|
2014-07-12 22:59:16 -05:00
|
|
|
// We do this so that we retain the ability to technically call
|
|
|
|
// process multiple times, even if we have no plans to do so
|
|
|
|
if m.oldUi != nil {
|
|
|
|
m.Ui = m.oldUi
|
|
|
|
}
|
2014-07-12 22:21:46 -05:00
|
|
|
|
2014-07-12 22:59:16 -05:00
|
|
|
// Set colorization
|
2014-07-17 11:34:32 -05:00
|
|
|
m.color = m.Color
|
2014-07-12 22:21:46 -05:00
|
|
|
for i, v := range args {
|
|
|
|
if v == "-no-color" {
|
2014-09-08 22:41:10 -05:00
|
|
|
m.color = false
|
2015-06-21 15:51:40 -05:00
|
|
|
m.Color = false
|
2014-07-12 22:59:16 -05:00
|
|
|
args = append(args[:i], args[i+1:]...)
|
|
|
|
break
|
2014-07-12 22:21:46 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-07-12 22:59:16 -05:00
|
|
|
// Set the UI
|
|
|
|
m.oldUi = m.Ui
|
2014-08-19 12:22:26 -05:00
|
|
|
m.Ui = &cli.ConcurrentUi{
|
|
|
|
Ui: &ColorizeUi{
|
|
|
|
Colorize: m.Colorize(),
|
|
|
|
ErrorColor: "[red]",
|
2015-03-05 14:22:34 -06:00
|
|
|
WarnColor: "[yellow]",
|
2014-08-19 12:22:26 -05:00
|
|
|
Ui: m.oldUi,
|
|
|
|
},
|
2014-07-12 22:59:16 -05:00
|
|
|
}
|
|
|
|
|
2014-08-05 11:32:01 -05:00
|
|
|
// If we support vars and the default var file exists, add it to
|
|
|
|
// the args...
|
2014-08-24 23:40:58 -05:00
|
|
|
m.autoKey = ""
|
2014-08-05 11:32:01 -05:00
|
|
|
if vars {
|
|
|
|
if _, err := os.Stat(DefaultVarsFilename); err == nil {
|
2014-09-08 22:57:08 -05:00
|
|
|
m.autoKey = "var-file-default"
|
2014-08-05 11:32:01 -05:00
|
|
|
args = append(args, "", "")
|
|
|
|
copy(args[2:], args[0:])
|
2014-08-24 23:40:58 -05:00
|
|
|
args[0] = "-" + m.autoKey
|
2014-08-05 11:32:01 -05:00
|
|
|
args[1] = DefaultVarsFilename
|
|
|
|
}
|
2015-03-02 11:21:45 -06:00
|
|
|
|
|
|
|
if _, err := os.Stat(DefaultVarsFilename + ".json"); err == nil {
|
|
|
|
m.autoKey = "var-file-default"
|
|
|
|
args = append(args, "", "")
|
|
|
|
copy(args[2:], args[0:])
|
|
|
|
args[0] = "-" + m.autoKey
|
|
|
|
args[1] = DefaultVarsFilename + ".json"
|
|
|
|
}
|
2014-08-05 11:32:01 -05:00
|
|
|
}
|
|
|
|
|
2014-07-12 22:21:46 -05:00
|
|
|
return args
|
|
|
|
}
|
2014-07-12 22:37:30 -05:00
|
|
|
|
|
|
|
// uiHook returns the UiHook to use with the context.
|
|
|
|
func (m *Meta) uiHook() *UiHook {
|
|
|
|
return &UiHook{
|
|
|
|
Colorize: m.Colorize(),
|
|
|
|
Ui: m.Ui,
|
|
|
|
}
|
|
|
|
}
|
2014-09-22 12:56:50 -05:00
|
|
|
|
2015-04-30 14:05:39 -05:00
|
|
|
const (
|
2016-01-20 12:50:33 -06:00
|
|
|
// ModuleDepthDefault is the default value for
|
|
|
|
// module depth, which can be overridden by flag
|
|
|
|
// or env var
|
|
|
|
ModuleDepthDefault = -1
|
|
|
|
|
|
|
|
// ModuleDepthEnvVar is the name of the environment variable that can be used to set module depth.
|
2015-04-30 14:05:39 -05:00
|
|
|
ModuleDepthEnvVar = "TF_MODULE_DEPTH"
|
|
|
|
)
|
|
|
|
|
|
|
|
func (m *Meta) addModuleDepthFlag(flags *flag.FlagSet, moduleDepth *int) {
|
2016-01-20 12:50:33 -06:00
|
|
|
flags.IntVar(moduleDepth, "module-depth", ModuleDepthDefault, "module-depth")
|
2015-04-30 14:05:39 -05:00
|
|
|
if envVar := os.Getenv(ModuleDepthEnvVar); envVar != "" {
|
|
|
|
if md, err := strconv.Atoi(envVar); err == nil {
|
|
|
|
*moduleDepth = md
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-11-03 16:46:26 -05:00
|
|
|
// outputShadowError outputs the error from ctx.ShadowError. If the
|
|
|
|
// error is nil then nothing happens. If output is false then it isn't
|
|
|
|
// outputted to the user (you can define logic to guard against outputting).
|
|
|
|
func (m *Meta) outputShadowError(err error, output bool) bool {
|
|
|
|
// Do nothing if no error
|
|
|
|
if err == nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// If not outputting, do nothing
|
|
|
|
if !output {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// Output!
|
|
|
|
m.Ui.Output(m.Colorize().Color(fmt.Sprintf(
|
|
|
|
"[reset][bold][yellow]\nExperimental feature failure! Please report a bug.\n\n"+
|
|
|
|
"This is not an error. Your Terraform operation completed successfully.\n"+
|
|
|
|
"Your real infrastructure is unaffected by this message.\n\n"+
|
|
|
|
"[reset][yellow]While running, Terraform sometimes tests experimental features in the\n"+
|
|
|
|
"background. These features cannot affect real state and never touch\n"+
|
|
|
|
"real infrastructure. If the features work properly, you see nothing.\n"+
|
|
|
|
"If the features fail, this message appears.\n\n"+
|
|
|
|
"The following failures happened while running experimental features.\n"+
|
|
|
|
"Please report a Terraform bug so that future Terraform versions that\n"+
|
|
|
|
"enable these features can be improved!\n\n"+
|
|
|
|
"You can report an issue at: https://github.com/hashicorp/terraform/issues\n\n"+
|
|
|
|
"%s\n\n"+
|
|
|
|
"This is not an error. Your terraform operation completed successfully\n"+
|
|
|
|
"and your real infrastructure is unaffected by this message.",
|
|
|
|
err,
|
|
|
|
)))
|
|
|
|
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2014-09-22 12:56:50 -05:00
|
|
|
// contextOpts are the options used to load a context from a command.
|
|
|
|
type contextOpts struct {
|
|
|
|
// Path to the directory where the root module is.
|
2016-11-02 12:30:28 -05:00
|
|
|
//
|
|
|
|
// PathEmptyOk, when set, will allow paths that have no Terraform
|
|
|
|
// configurations. The result in that case will be an empty module.
|
|
|
|
Path string
|
|
|
|
PathEmptyOk bool
|
2014-09-22 12:56:50 -05:00
|
|
|
|
|
|
|
// StatePath is the path to the state file. If this is empty, then
|
|
|
|
// no state will be loaded. It is also okay for this to be a path to
|
|
|
|
// a file that doesn't exist; it is assumed that this means that there
|
|
|
|
// is simply no state.
|
|
|
|
StatePath string
|
|
|
|
|
|
|
|
// GetMode is the module.GetMode to use when loading the module tree.
|
|
|
|
GetMode module.GetMode
|
2015-03-24 11:18:15 -05:00
|
|
|
|
|
|
|
// Set to true when running a destroy plan/apply.
|
|
|
|
Destroy bool
|
2015-05-06 10:58:42 -05:00
|
|
|
|
|
|
|
// Number of concurrent operations allowed
|
|
|
|
Parallelism int
|
2014-09-22 12:56:50 -05:00
|
|
|
}
|