2015-02-05 00:44:23 +01:00
|
|
|
package terraform
|
|
|
|
|
|
|
|
import (
|
2015-02-14 01:27:23 +01:00
|
|
|
"fmt"
|
2015-02-17 04:04:05 +01:00
|
|
|
"log"
|
2015-02-14 02:29:38 +01:00
|
|
|
"sort"
|
2015-05-14 05:09:05 +02:00
|
|
|
"strings"
|
2015-02-05 00:44:23 +01:00
|
|
|
"sync"
|
|
|
|
|
2015-02-07 18:53:46 +01:00
|
|
|
"github.com/hashicorp/go-multierror"
|
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-21 03:38:26 +02:00
|
|
|
"github.com/hashicorp/hcl"
|
2015-02-14 02:29:38 +01:00
|
|
|
"github.com/hashicorp/terraform/config"
|
2015-02-05 00:44:23 +01:00
|
|
|
"github.com/hashicorp/terraform/config/module"
|
2016-10-26 21:46:22 +02:00
|
|
|
"github.com/hashicorp/terraform/helper/experiment"
|
2016-09-15 05:59:02 +02:00
|
|
|
)
|
|
|
|
|
2016-10-19 22:41:30 +02:00
|
|
|
// InputMode defines what sort of input will be asked for when Input
|
|
|
|
// is called on Context.
|
|
|
|
type InputMode byte
|
|
|
|
|
2015-02-14 02:29:38 +01:00
|
|
|
const (
|
2015-03-07 00:04:12 +01:00
|
|
|
// InputModeVar asks for all variables
|
2015-02-14 02:29:38 +01:00
|
|
|
InputModeVar InputMode = 1 << iota
|
|
|
|
|
2016-11-02 03:16:43 +01:00
|
|
|
// InputModeVarUnset asks for variables which are not set yet.
|
|
|
|
// InputModeVar must be set for this to have an effect.
|
2015-03-07 00:04:12 +01:00
|
|
|
InputModeVarUnset
|
|
|
|
|
2015-02-14 02:29:38 +01:00
|
|
|
// InputModeProvider asks for provider variables
|
|
|
|
InputModeProvider
|
|
|
|
|
|
|
|
// InputModeStd is the standard operating mode and asks for both variables
|
|
|
|
// and providers.
|
|
|
|
InputModeStd = InputModeVar | InputModeProvider
|
|
|
|
)
|
|
|
|
|
2016-10-02 01:51:00 +02:00
|
|
|
var (
|
|
|
|
// contextFailOnShadowError will cause Context operations to return
|
|
|
|
// errors when shadow operations fail. This is only used for testing.
|
|
|
|
contextFailOnShadowError = false
|
2016-10-02 06:30:09 +02:00
|
|
|
|
|
|
|
// contextTestDeepCopyOnPlan will perform a Diff DeepCopy on every
|
|
|
|
// Plan operation, effectively testing the Diff DeepCopy whenever
|
|
|
|
// a Plan occurs. This is enabled for tests.
|
|
|
|
contextTestDeepCopyOnPlan = false
|
2016-10-02 01:51:00 +02:00
|
|
|
)
|
|
|
|
|
2015-02-05 00:44:23 +01:00
|
|
|
// ContextOpts are the user-configurable options to create a context with
|
|
|
|
// NewContext.
|
|
|
|
type ContextOpts struct {
|
2016-03-11 20:07:54 +01:00
|
|
|
Destroy bool
|
|
|
|
Diff *Diff
|
|
|
|
Hooks []Hook
|
|
|
|
Module *module.Tree
|
|
|
|
Parallelism int
|
|
|
|
State *State
|
|
|
|
StateFutureAllowed bool
|
|
|
|
Providers map[string]ResourceProviderFactory
|
|
|
|
Provisioners map[string]ResourceProvisionerFactory
|
2016-10-21 23:25:05 +02:00
|
|
|
Shadow bool
|
2016-03-11 20:07:54 +01:00
|
|
|
Targets []string
|
2016-07-18 19:52:10 +02:00
|
|
|
Variables map[string]interface{}
|
2015-02-05 00:44:23 +01:00
|
|
|
|
|
|
|
UIInput UIInput
|
|
|
|
}
|
|
|
|
|
|
|
|
// Context represents all the context that Terraform needs in order to
|
|
|
|
// perform operations on infrastructure. This structure is built using
|
|
|
|
// NewContext. See the documentation for that.
|
2016-04-26 19:38:30 +02:00
|
|
|
//
|
|
|
|
// Extra functions on Context can be found in context_*.go files.
|
2015-02-14 03:15:36 +01:00
|
|
|
type Context struct {
|
2016-10-03 09:01:11 +02:00
|
|
|
// Maintainer note: Anytime this struct is changed, please verify
|
|
|
|
// that newShadowContext still does the right thing. Tests should
|
|
|
|
// fail regardless but putting this note here as well.
|
|
|
|
|
2016-10-04 03:23:37 +02:00
|
|
|
components contextComponentFactory
|
|
|
|
destroy bool
|
|
|
|
diff *Diff
|
|
|
|
diffLock sync.RWMutex
|
|
|
|
hooks []Hook
|
|
|
|
module *module.Tree
|
|
|
|
sh *stopHook
|
2016-10-21 23:25:05 +02:00
|
|
|
shadow bool
|
2016-10-04 03:23:37 +02:00
|
|
|
state *State
|
|
|
|
stateLock sync.RWMutex
|
|
|
|
targets []string
|
|
|
|
uiInput UIInput
|
|
|
|
variables map[string]interface{}
|
2015-02-13 18:05:09 +01:00
|
|
|
|
2015-02-14 02:59:54 +01:00
|
|
|
l sync.Mutex // Lock acquired during any task
|
2015-02-17 05:27:29 +01:00
|
|
|
parallelSem Semaphore
|
2015-02-14 02:59:54 +01:00
|
|
|
providerInputConfig map[string]map[string]interface{}
|
|
|
|
runCh <-chan struct{}
|
2016-10-24 02:55:45 +02:00
|
|
|
stopCh chan struct{}
|
2016-10-02 01:51:00 +02:00
|
|
|
shadowErr error
|
2015-02-05 00:44:23 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// NewContext creates a new Context structure.
|
|
|
|
//
|
|
|
|
// Once a Context is creator, the pointer values within ContextOpts
|
|
|
|
// should not be mutated in any way, since the pointers are copied, not
|
|
|
|
// the values themselves.
|
2016-03-11 20:07:54 +01:00
|
|
|
func NewContext(opts *ContextOpts) (*Context, error) {
|
2016-11-13 01:50:26 +01:00
|
|
|
// Validate the version requirement if it is given
|
|
|
|
if opts.Module != nil {
|
|
|
|
if err := checkRequiredVersion(opts.Module); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-13 18:05:09 +01:00
|
|
|
// Copy all the hooks and add our stop hook. We don't append directly
|
|
|
|
// to the Config so that we're not modifying that in-place.
|
|
|
|
sh := new(stopHook)
|
|
|
|
hooks := make([]Hook, len(opts.Hooks)+1)
|
|
|
|
copy(hooks, opts.Hooks)
|
|
|
|
hooks[len(opts.Hooks)] = sh
|
|
|
|
|
2015-02-12 00:22:03 +01:00
|
|
|
state := opts.State
|
|
|
|
if state == nil {
|
|
|
|
state = new(State)
|
|
|
|
state.init()
|
|
|
|
}
|
|
|
|
|
2016-03-11 20:07:54 +01:00
|
|
|
// If our state is from the future, then error. Callers can avoid
|
|
|
|
// this error by explicitly setting `StateFutureAllowed`.
|
|
|
|
if !opts.StateFutureAllowed && state.FromFutureTerraform() {
|
|
|
|
return nil, fmt.Errorf(
|
|
|
|
"Terraform doesn't allow running any operations against a state\n"+
|
|
|
|
"that was written by a future Terraform version. The state is\n"+
|
|
|
|
"reporting it is written by Terraform '%s'.\n\n"+
|
|
|
|
"Please run at least that version of Terraform to continue.",
|
|
|
|
state.TFVersion)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Explicitly reset our state version to our current version so that
|
|
|
|
// any operations we do will write out that our latest version
|
|
|
|
// has run.
|
|
|
|
state.TFVersion = Version
|
|
|
|
|
2015-02-17 05:27:29 +01:00
|
|
|
// Determine parallelism, default to 10. We do this both to limit
|
|
|
|
// CPU pressure but also to have an extra guard against rate throttling
|
|
|
|
// from providers.
|
|
|
|
par := opts.Parallelism
|
|
|
|
if par == 0 {
|
|
|
|
par = 10
|
|
|
|
}
|
|
|
|
|
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-21 03:38:26 +02:00
|
|
|
// Set up the variables in the following sequence:
|
|
|
|
// 0 - Take default values from the configuration
|
|
|
|
// 1 - Take values from TF_VAR_x environment variables
|
|
|
|
// 2 - Take values specified in -var flags, overriding values
|
|
|
|
// set by environment variables if necessary. This includes
|
|
|
|
// values taken from -var-file in addition.
|
2016-07-18 19:52:10 +02:00
|
|
|
variables := make(map[string]interface{})
|
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-21 03:38:26 +02:00
|
|
|
|
|
|
|
if opts.Module != nil {
|
2016-08-17 20:10:26 +02:00
|
|
|
var err error
|
|
|
|
variables, err = Variables(opts.Module, opts.Variables)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
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-21 03:38:26 +02:00
|
|
|
}
|
2015-05-14 18:58:30 +02:00
|
|
|
}
|
2015-05-14 05:09:05 +02:00
|
|
|
|
2015-02-14 03:15:36 +01:00
|
|
|
return &Context{
|
2016-10-04 03:23:37 +02:00
|
|
|
components: &basicComponentFactory{
|
|
|
|
providers: opts.Providers,
|
|
|
|
provisioners: opts.Provisioners,
|
|
|
|
},
|
|
|
|
destroy: opts.Destroy,
|
|
|
|
diff: opts.Diff,
|
|
|
|
hooks: hooks,
|
|
|
|
module: opts.Module,
|
2016-10-21 23:25:05 +02:00
|
|
|
shadow: opts.Shadow,
|
2016-10-04 03:23:37 +02:00
|
|
|
state: state,
|
|
|
|
targets: opts.Targets,
|
|
|
|
uiInput: opts.UIInput,
|
|
|
|
variables: variables,
|
2015-02-17 05:27:29 +01:00
|
|
|
|
|
|
|
parallelSem: NewSemaphore(par),
|
2015-02-14 02:59:54 +01:00
|
|
|
providerInputConfig: make(map[string]map[string]interface{}),
|
|
|
|
sh: sh,
|
2016-03-11 20:07:54 +01:00
|
|
|
}, nil
|
2015-02-05 00:44:23 +01:00
|
|
|
}
|
|
|
|
|
2015-04-23 17:52:31 +02:00
|
|
|
type ContextGraphOpts struct {
|
|
|
|
Validate bool
|
|
|
|
Verbose bool
|
|
|
|
}
|
|
|
|
|
2015-02-14 03:22:08 +01:00
|
|
|
// Graph returns the graph for this config.
|
2015-04-23 17:52:31 +02:00
|
|
|
func (c *Context) Graph(g *ContextGraphOpts) (*Graph, error) {
|
|
|
|
return c.graphBuilder(g).Build(RootModulePath)
|
2015-02-14 03:22:08 +01:00
|
|
|
}
|
|
|
|
|
2015-02-05 00:44:23 +01:00
|
|
|
// GraphBuilder returns the GraphBuilder that will be used to create
|
|
|
|
// the graphs for this context.
|
2015-04-23 17:52:31 +02:00
|
|
|
func (c *Context) graphBuilder(g *ContextGraphOpts) GraphBuilder {
|
2015-02-05 00:44:23 +01:00
|
|
|
return &BuiltinGraphBuilder{
|
2015-02-09 20:15:54 +01:00
|
|
|
Root: c.module,
|
2015-02-13 21:05:34 +01:00
|
|
|
Diff: c.diff,
|
2016-10-04 03:23:37 +02:00
|
|
|
Providers: c.components.ResourceProviders(),
|
|
|
|
Provisioners: c.components.ResourceProvisioners(),
|
2015-02-09 20:15:54 +01:00
|
|
|
State: c.state,
|
2015-03-24 17:18:15 +01:00
|
|
|
Targets: c.targets,
|
|
|
|
Destroy: c.destroy,
|
2015-04-23 17:52:31 +02:00
|
|
|
Validate: g.Validate,
|
|
|
|
Verbose: g.Verbose,
|
2015-02-05 00:44:23 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-10-02 01:51:00 +02:00
|
|
|
// ShadowError returns any errors caught during a shadow operation.
|
|
|
|
//
|
|
|
|
// A shadow operation is an operation run in parallel to a real operation
|
|
|
|
// that performs the same tasks using new logic on copied state. The results
|
|
|
|
// are compared to ensure that the new logic works the same as the old logic.
|
|
|
|
// The shadow never affects the real operation or return values.
|
|
|
|
//
|
|
|
|
// The result of the shadow operation are only available through this function
|
|
|
|
// call after a real operation is complete.
|
|
|
|
//
|
|
|
|
// For API consumers of Context, you can safely ignore this function
|
|
|
|
// completely if you have no interest in helping report experimental feature
|
|
|
|
// errors to Terraform maintainers. Otherwise, please call this function
|
|
|
|
// after every operation and report this to the user.
|
|
|
|
//
|
|
|
|
// IMPORTANT: Shadow errors are _never_ critical: they _never_ affect
|
|
|
|
// the real state or result of a real operation. They are purely informational
|
|
|
|
// to assist in future Terraform versions being more stable. Please message
|
|
|
|
// this effectively to the end user.
|
|
|
|
//
|
|
|
|
// This must be called only when no other operation is running (refresh,
|
|
|
|
// plan, etc.). The result can be used in parallel to any other operation
|
|
|
|
// running.
|
|
|
|
func (c *Context) ShadowError() error {
|
|
|
|
return c.shadowErr
|
|
|
|
}
|
|
|
|
|
2016-11-14 07:04:11 +01:00
|
|
|
// Interpolater returns an Interpolater built on a copy of the state
|
|
|
|
// that can be used to test interpolation values.
|
|
|
|
func (c *Context) Interpolater() *Interpolater {
|
|
|
|
var varLock sync.Mutex
|
|
|
|
var stateLock sync.RWMutex
|
|
|
|
return &Interpolater{
|
|
|
|
Operation: walkApply,
|
|
|
|
Module: c.module,
|
|
|
|
State: c.state.DeepCopy(),
|
|
|
|
StateLock: &stateLock,
|
|
|
|
VariableValues: map[string]interface{}{},
|
|
|
|
VariableValuesLock: &varLock,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-14 02:29:38 +01:00
|
|
|
// Input asks for input to fill variables and provider configurations.
|
|
|
|
// This modifies the configuration in-place, so asking for Input twice
|
|
|
|
// may result in different UI output showing different current values.
|
2015-02-14 03:15:36 +01:00
|
|
|
func (c *Context) Input(mode InputMode) error {
|
2016-11-04 16:30:51 +01:00
|
|
|
v := c.acquireRun("input")
|
2015-02-14 02:29:38 +01:00
|
|
|
defer c.releaseRun(v)
|
|
|
|
|
|
|
|
if mode&InputModeVar != 0 {
|
|
|
|
// Walk the variables first for the root module. We walk them in
|
|
|
|
// alphabetical order for UX reasons.
|
|
|
|
rootConf := c.module.Config()
|
|
|
|
names := make([]string, len(rootConf.Variables))
|
|
|
|
m := make(map[string]*config.Variable)
|
|
|
|
for i, v := range rootConf.Variables {
|
|
|
|
names[i] = v.Name
|
|
|
|
m[v.Name] = v
|
|
|
|
}
|
|
|
|
sort.Strings(names)
|
|
|
|
for _, n := range names {
|
2015-07-02 17:22:11 +02:00
|
|
|
// If we only care about unset variables, then if the variable
|
2015-03-07 00:04:12 +01:00
|
|
|
// is set, continue on.
|
|
|
|
if mode&InputModeVarUnset != 0 {
|
|
|
|
if _, ok := c.variables[n]; ok {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-08-09 22:14:40 +02:00
|
|
|
var valueType config.VariableType
|
|
|
|
|
2015-02-14 02:29:38 +01:00
|
|
|
v := m[n]
|
2016-08-09 22:14:40 +02:00
|
|
|
switch valueType = v.Type(); valueType {
|
2015-04-19 01:31:21 +02:00
|
|
|
case config.VariableTypeUnknown:
|
|
|
|
continue
|
2015-02-14 02:29:38 +01:00
|
|
|
case config.VariableTypeMap:
|
2016-08-09 22:14:40 +02:00
|
|
|
// OK
|
core: support native list variables in config
This commit adds support for native list variables and outputs, building
up on the previous change to state. Interpolation functions now return
native lists in preference to StringList.
List variables are defined like this:
variable "test" {
# This can also be inferred
type = "list"
default = ["Hello", "World"]
}
output "test_out" {
value = "${var.a_list}"
}
This results in the following state:
```
...
"outputs": {
"test_out": [
"hello",
"world"
]
},
...
```
And the result of terraform output is as follows:
```
$ terraform output
test_out = [
hello
world
]
```
Using the output name, an xargs-friendly representation is output:
```
$ terraform output test_out
hello
world
```
The output command also supports indexing into the list (with
appropriate range checking and no wrapping):
```
$ terraform output test_out 1
world
```
Along with maps, list outputs from one module may be passed as variables
into another, removing the need for the `join(",", var.list_as_string)`
and `split(",", var.list_as_string)` which was previously necessary in
Terraform configuration.
This commit also updates the tests and implementations of built-in
interpolation functions to take and return native lists where
appropriate.
A backwards compatibility note: previously the concat interpolation
function was capable of concatenating either strings or lists. The
strings use case was deprectated a long time ago but still remained.
Because we cannot return `ast.TypeAny` from an interpolation function,
this use case is no longer supported for strings - `concat` is only
capable of concatenating lists. This should not be a huge issue - the
type checker picks up incorrect parameters, and the native HIL string
concatenation - or the `join` function - can be used to replicate the
missing behaviour.
2016-04-22 02:03:24 +02:00
|
|
|
case config.VariableTypeList:
|
2016-08-09 22:14:40 +02:00
|
|
|
// OK
|
2015-02-14 02:29:38 +01:00
|
|
|
case config.VariableTypeString:
|
2016-08-09 22:14:40 +02:00
|
|
|
// OK
|
2015-02-14 02:29:38 +01:00
|
|
|
default:
|
|
|
|
panic(fmt.Sprintf("Unknown variable type: %#v", v.Type()))
|
|
|
|
}
|
|
|
|
|
2015-07-02 17:22:11 +02:00
|
|
|
// If the variable is not already set, and the variable defines a
|
|
|
|
// default, use that for the value.
|
|
|
|
if _, ok := c.variables[n]; !ok {
|
|
|
|
if v.Default != nil {
|
|
|
|
c.variables[n] = v.Default.(string)
|
|
|
|
continue
|
|
|
|
}
|
2015-02-14 02:29:38 +01:00
|
|
|
}
|
|
|
|
|
2016-08-09 22:14:40 +02:00
|
|
|
// this should only happen during tests
|
|
|
|
if c.uiInput == nil {
|
|
|
|
log.Println("[WARN] Content.uiInput is nil")
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2015-02-14 02:29:38 +01:00
|
|
|
// Ask the user for a value for this variable
|
|
|
|
var value string
|
2016-07-28 03:34:30 +02:00
|
|
|
retry := 0
|
2015-02-14 02:29:38 +01:00
|
|
|
for {
|
|
|
|
var err error
|
|
|
|
value, err = c.uiInput.Input(&InputOpts{
|
|
|
|
Id: fmt.Sprintf("var.%s", n),
|
|
|
|
Query: fmt.Sprintf("var.%s", n),
|
|
|
|
Description: v.Description,
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf(
|
|
|
|
"Error asking for %s: %s", n, err)
|
|
|
|
}
|
|
|
|
|
2016-08-10 22:34:21 +02:00
|
|
|
if value == "" && v.Required() {
|
|
|
|
// Redo if it is required, but abort if we keep getting
|
|
|
|
// blank entries
|
|
|
|
if retry > 2 {
|
|
|
|
return fmt.Errorf("missing required value for %q", n)
|
2016-08-09 22:14:40 +02:00
|
|
|
}
|
2016-08-10 22:34:21 +02:00
|
|
|
retry++
|
|
|
|
continue
|
2015-02-14 02:29:38 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
2016-08-09 22:14:40 +02:00
|
|
|
// no value provided, so don't set the variable at all
|
|
|
|
if value == "" {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
decoded, err := parseVariableAsHCL(n, value, valueType)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if decoded != nil {
|
|
|
|
c.variables[n] = decoded
|
2015-02-14 02:29:38 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-02-14 02:59:54 +01:00
|
|
|
if mode&InputModeProvider != 0 {
|
2015-04-23 17:52:31 +02:00
|
|
|
// Build the graph
|
|
|
|
graph, err := c.Graph(&ContextGraphOpts{Validate: true})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2015-02-14 02:59:54 +01:00
|
|
|
// Do the walk
|
2016-10-02 01:51:00 +02:00
|
|
|
if _, err := c.walk(graph, nil, walkInput); err != nil {
|
2015-02-14 02:59:54 +01:00
|
|
|
return err
|
2015-02-14 02:29:38 +01:00
|
|
|
}
|
2015-02-14 02:59:54 +01:00
|
|
|
}
|
2015-02-14 02:29:38 +01:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-02-12 23:46:22 +01:00
|
|
|
// Apply applies the changes represented by this context and returns
|
|
|
|
// the resulting state.
|
|
|
|
//
|
|
|
|
// In addition to returning the resulting state, this context is updated
|
|
|
|
// with the latest state.
|
2015-02-14 03:15:36 +01:00
|
|
|
func (c *Context) Apply() (*State, error) {
|
2016-11-04 16:30:51 +01:00
|
|
|
v := c.acquireRun("apply")
|
2015-02-13 18:05:09 +01:00
|
|
|
defer c.releaseRun(v)
|
|
|
|
|
2015-02-12 23:46:22 +01:00
|
|
|
// Copy our own state
|
2015-02-24 06:32:27 +01:00
|
|
|
c.state = c.state.DeepCopy()
|
2015-02-12 23:46:22 +01:00
|
|
|
|
2016-11-10 16:54:39 +01:00
|
|
|
// Enable the new graph by default
|
|
|
|
X_legacyGraph := experiment.Enabled(experiment.X_legacyGraph)
|
|
|
|
|
|
|
|
// Build the graph.
|
|
|
|
var graph *Graph
|
|
|
|
var err error
|
|
|
|
if !X_legacyGraph {
|
|
|
|
graph, err = (&ApplyGraphBuilder{
|
|
|
|
Module: c.module,
|
|
|
|
Diff: c.diff,
|
|
|
|
State: c.state,
|
|
|
|
Providers: c.components.ResourceProviders(),
|
|
|
|
Provisioners: c.components.ResourceProvisioners(),
|
|
|
|
Destroy: c.destroy,
|
|
|
|
}).Build(RootModulePath)
|
|
|
|
} else {
|
|
|
|
graph, err = c.Graph(&ContextGraphOpts{Validate: true})
|
2016-09-13 23:34:15 +02:00
|
|
|
}
|
2015-04-23 17:52:31 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2016-10-19 23:17:12 +02:00
|
|
|
// Determine the operation
|
|
|
|
operation := walkApply
|
|
|
|
if c.destroy {
|
|
|
|
operation = walkDestroy
|
|
|
|
}
|
|
|
|
|
|
|
|
// Walk the graph
|
2016-11-10 16:54:39 +01:00
|
|
|
walker, err := c.walk(graph, graph, operation)
|
2016-07-01 01:22:20 +02:00
|
|
|
if len(walker.ValidationErrors) > 0 {
|
|
|
|
err = multierror.Append(err, walker.ValidationErrors...)
|
2015-10-02 20:18:33 +02:00
|
|
|
}
|
2015-02-12 23:46:22 +01:00
|
|
|
|
|
|
|
// Clean out any unused things
|
|
|
|
c.state.prune()
|
|
|
|
|
|
|
|
return c.state, err
|
|
|
|
}
|
|
|
|
|
2015-02-12 00:22:03 +01:00
|
|
|
// Plan generates an execution plan for the given context.
|
|
|
|
//
|
|
|
|
// The execution plan encapsulates the context and can be stored
|
|
|
|
// in order to reinstantiate a context later for Apply.
|
|
|
|
//
|
|
|
|
// Plan also updates the diff of this context to be the diff generated
|
|
|
|
// by the plan, so Apply can be called after.
|
2015-03-24 17:18:15 +01:00
|
|
|
func (c *Context) Plan() (*Plan, error) {
|
2016-11-04 16:30:51 +01:00
|
|
|
v := c.acquireRun("plan")
|
2015-02-13 18:05:09 +01:00
|
|
|
defer c.releaseRun(v)
|
|
|
|
|
2015-02-12 00:22:03 +01:00
|
|
|
p := &Plan{
|
2016-02-23 17:24:53 +01:00
|
|
|
Module: c.module,
|
|
|
|
Vars: c.variables,
|
|
|
|
State: c.state,
|
|
|
|
Targets: c.targets,
|
2015-02-12 00:22:03 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
var operation walkOperation
|
2015-03-24 17:18:15 +01:00
|
|
|
if c.destroy {
|
2015-02-12 00:22:03 +01:00
|
|
|
operation = walkPlanDestroy
|
|
|
|
} else {
|
|
|
|
// Set our state to be something temporary. We do this so that
|
|
|
|
// the plan can update a fake state so that variables work, then
|
|
|
|
// we replace it back with our old state.
|
|
|
|
old := c.state
|
|
|
|
if old == nil {
|
|
|
|
c.state = &State{}
|
|
|
|
c.state.init()
|
|
|
|
} else {
|
2015-02-24 06:32:27 +01:00
|
|
|
c.state = old.DeepCopy()
|
2015-02-12 00:22:03 +01:00
|
|
|
}
|
|
|
|
defer func() {
|
|
|
|
c.state = old
|
|
|
|
}()
|
|
|
|
|
|
|
|
operation = walkPlan
|
|
|
|
}
|
|
|
|
|
2015-02-13 04:34:21 +01:00
|
|
|
// Setup our diff
|
|
|
|
c.diffLock.Lock()
|
|
|
|
c.diff = new(Diff)
|
|
|
|
c.diff.init()
|
|
|
|
c.diffLock.Unlock()
|
|
|
|
|
2016-10-26 21:46:22 +02:00
|
|
|
// Used throughout below
|
2016-11-10 16:54:39 +01:00
|
|
|
X_legacyGraph := experiment.Enabled(experiment.X_legacyGraph)
|
|
|
|
|
|
|
|
// Build the graph.
|
|
|
|
var graph *Graph
|
|
|
|
var err error
|
|
|
|
if !X_legacyGraph {
|
|
|
|
if c.destroy {
|
|
|
|
graph, err = (&DestroyPlanGraphBuilder{
|
|
|
|
Module: c.module,
|
|
|
|
State: c.state,
|
|
|
|
Targets: c.targets,
|
|
|
|
}).Build(RootModulePath)
|
|
|
|
} else {
|
|
|
|
graph, err = (&PlanGraphBuilder{
|
|
|
|
Module: c.module,
|
|
|
|
State: c.state,
|
|
|
|
Providers: c.components.ResourceProviders(),
|
|
|
|
Targets: c.targets,
|
|
|
|
}).Build(RootModulePath)
|
|
|
|
}
|
2016-10-19 09:59:56 +02:00
|
|
|
} else {
|
2016-11-10 16:54:39 +01:00
|
|
|
graph, err = c.Graph(&ContextGraphOpts{Validate: true})
|
2016-10-19 09:59:56 +02:00
|
|
|
}
|
2015-04-23 17:52:31 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2015-02-12 00:22:03 +01:00
|
|
|
// Do the walk
|
2016-11-10 16:54:39 +01:00
|
|
|
walker, err := c.walk(graph, graph, operation)
|
2016-07-01 01:22:20 +02:00
|
|
|
if err != nil {
|
2015-02-12 02:01:08 +01:00
|
|
|
return nil, err
|
|
|
|
}
|
2015-02-13 04:34:21 +01:00
|
|
|
p.Diff = c.diff
|
2015-02-12 00:22:03 +01:00
|
|
|
|
2016-10-02 06:30:09 +02:00
|
|
|
// If this is true, it means we're running unit tests. In this case,
|
|
|
|
// we perform a deep copy just to ensure that all context tests also
|
|
|
|
// test that a diff is copy-able. This will panic if it fails. This
|
|
|
|
// is enabled during unit tests.
|
|
|
|
//
|
|
|
|
// This should never be true during production usage, but even if it is,
|
|
|
|
// it can't do any real harm.
|
|
|
|
if contextTestDeepCopyOnPlan {
|
|
|
|
p.Diff.DeepCopy()
|
|
|
|
}
|
|
|
|
|
2016-10-19 09:59:56 +02:00
|
|
|
// We don't do the reverification during the new destroy plan because
|
|
|
|
// it will use a different apply process.
|
2016-11-10 16:54:39 +01:00
|
|
|
if X_legacyGraph {
|
2016-10-19 09:59:56 +02:00
|
|
|
// Now that we have a diff, we can build the exact graph that Apply will use
|
|
|
|
// and catch any possible cycles during the Plan phase.
|
|
|
|
if _, err := c.Graph(&ContextGraphOpts{Validate: true}); err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2015-05-06 00:24:44 +02:00
|
|
|
}
|
2016-10-19 09:59:56 +02:00
|
|
|
|
2016-07-01 01:22:20 +02:00
|
|
|
var errs error
|
|
|
|
if len(walker.ValidationErrors) > 0 {
|
|
|
|
errs = multierror.Append(errs, walker.ValidationErrors...)
|
|
|
|
}
|
|
|
|
return p, errs
|
2015-02-12 00:22:03 +01:00
|
|
|
}
|
|
|
|
|
2015-02-10 17:58:14 +01:00
|
|
|
// Refresh goes through all the resources in the state and refreshes them
|
|
|
|
// to their latest state. This will update the state that this context
|
|
|
|
// works with, along with returning it.
|
|
|
|
//
|
|
|
|
// Even in the case an error is returned, the state will be returned and
|
|
|
|
// will potentially be partially updated.
|
2015-02-14 03:19:41 +01:00
|
|
|
func (c *Context) Refresh() (*State, error) {
|
2016-11-04 16:30:51 +01:00
|
|
|
v := c.acquireRun("refresh")
|
2015-02-13 18:05:09 +01:00
|
|
|
defer c.releaseRun(v)
|
|
|
|
|
2015-02-11 22:52:13 +01:00
|
|
|
// Copy our own state
|
2015-02-24 06:32:27 +01:00
|
|
|
c.state = c.state.DeepCopy()
|
2015-02-11 22:52:13 +01:00
|
|
|
|
2015-04-23 17:52:31 +02:00
|
|
|
// Build the graph
|
|
|
|
graph, err := c.Graph(&ContextGraphOpts{Validate: true})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2015-02-11 22:52:13 +01:00
|
|
|
// Do the walk
|
2016-10-12 12:57:52 +02:00
|
|
|
if _, err := c.walk(graph, graph, walkRefresh); err != nil {
|
2015-02-14 03:19:41 +01:00
|
|
|
return nil, err
|
2015-02-10 17:58:14 +01:00
|
|
|
}
|
|
|
|
|
2015-02-11 17:51:17 +01:00
|
|
|
// Clean out any unused things
|
|
|
|
c.state.prune()
|
|
|
|
|
2015-02-11 17:48:45 +01:00
|
|
|
return c.state, nil
|
2015-02-10 17:58:14 +01:00
|
|
|
}
|
|
|
|
|
2015-02-13 18:05:09 +01:00
|
|
|
// Stop stops the running task.
|
|
|
|
//
|
|
|
|
// Stop will block until the task completes.
|
2015-02-14 03:15:36 +01:00
|
|
|
func (c *Context) Stop() {
|
2015-02-13 18:05:09 +01:00
|
|
|
c.l.Lock()
|
|
|
|
ch := c.runCh
|
|
|
|
|
|
|
|
// If we aren't running, then just return
|
|
|
|
if ch == nil {
|
|
|
|
c.l.Unlock()
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Tell the hook we want to stop
|
|
|
|
c.sh.Stop()
|
|
|
|
|
2016-10-24 02:55:45 +02:00
|
|
|
// Close the stop channel
|
|
|
|
close(c.stopCh)
|
|
|
|
|
2015-02-13 18:05:09 +01:00
|
|
|
// Wait for us to stop
|
|
|
|
c.l.Unlock()
|
|
|
|
<-ch
|
|
|
|
}
|
|
|
|
|
2015-02-05 00:44:23 +01:00
|
|
|
// Validate validates the configuration and returns any warnings or errors.
|
2015-02-14 03:15:36 +01:00
|
|
|
func (c *Context) Validate() ([]string, []error) {
|
2016-11-04 16:30:51 +01:00
|
|
|
v := c.acquireRun("validate")
|
2015-02-13 18:05:09 +01:00
|
|
|
defer c.releaseRun(v)
|
|
|
|
|
2015-02-07 18:53:46 +01:00
|
|
|
var errs error
|
2015-02-05 02:02:18 +01:00
|
|
|
|
|
|
|
// Validate the configuration itself
|
|
|
|
if err := c.module.Validate(); err != nil {
|
2015-02-07 18:53:46 +01:00
|
|
|
errs = multierror.Append(errs, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// This only needs to be done for the root module, since inter-module
|
|
|
|
// variables are validated in the module tree.
|
|
|
|
if config := c.module.Config(); config != nil {
|
|
|
|
// Validate the user variables
|
|
|
|
if err := smcUserVariables(config, c.variables); len(err) > 0 {
|
|
|
|
errs = multierror.Append(errs, err...)
|
|
|
|
}
|
2015-02-05 02:02:18 +01:00
|
|
|
}
|
|
|
|
|
2015-06-25 07:43:55 +02:00
|
|
|
// If we have errors at this point, the graphing has no chance,
|
|
|
|
// so just bail early.
|
|
|
|
if errs != nil {
|
|
|
|
return nil, []error{errs}
|
|
|
|
}
|
|
|
|
|
2015-05-06 00:24:44 +02:00
|
|
|
// Build the graph so we can walk it and run Validate on nodes.
|
|
|
|
// We also validate the graph generated here, but this graph doesn't
|
|
|
|
// necessarily match the graph that Plan will generate, so we'll validate the
|
|
|
|
// graph again later after Planning.
|
|
|
|
graph, err := c.Graph(&ContextGraphOpts{Validate: true})
|
2015-04-23 17:52:31 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, []error{err}
|
|
|
|
}
|
|
|
|
|
2015-02-10 17:58:14 +01:00
|
|
|
// Walk
|
2016-10-12 12:57:52 +02:00
|
|
|
walker, err := c.walk(graph, graph, walkValidate)
|
2015-02-05 00:44:23 +01:00
|
|
|
if err != nil {
|
2015-02-07 18:55:11 +01:00
|
|
|
return nil, multierror.Append(errs, err).Errors
|
2015-02-05 00:44:23 +01:00
|
|
|
}
|
|
|
|
|
2015-02-07 22:29:55 +01:00
|
|
|
// Return the result
|
|
|
|
rerrs := multierror.Append(errs, walker.ValidationErrors...)
|
|
|
|
return walker.ValidationWarnings, rerrs.Errors
|
2015-02-05 00:44:23 +01:00
|
|
|
}
|
2015-02-10 17:58:14 +01:00
|
|
|
|
2015-03-07 00:12:39 +01:00
|
|
|
// Module returns the module tree associated with this context.
|
|
|
|
func (c *Context) Module() *module.Tree {
|
|
|
|
return c.module
|
|
|
|
}
|
|
|
|
|
2015-03-07 00:04:12 +01:00
|
|
|
// Variables will return the mapping of variables that were defined
|
|
|
|
// for this Context. If Input was called, this mapping may be different
|
|
|
|
// than what was given.
|
2016-07-18 19:52:10 +02:00
|
|
|
func (c *Context) Variables() map[string]interface{} {
|
2015-03-07 00:04:12 +01:00
|
|
|
return c.variables
|
|
|
|
}
|
|
|
|
|
|
|
|
// SetVariable sets a variable after a context has already been built.
|
2016-07-18 19:52:10 +02:00
|
|
|
func (c *Context) SetVariable(k string, v interface{}) {
|
2015-03-07 00:04:12 +01:00
|
|
|
c.variables[k] = v
|
|
|
|
}
|
|
|
|
|
2016-11-04 16:30:51 +01:00
|
|
|
func (c *Context) acquireRun(phase string) chan<- struct{} {
|
2015-02-13 18:05:09 +01:00
|
|
|
c.l.Lock()
|
|
|
|
defer c.l.Unlock()
|
|
|
|
|
2016-11-04 16:30:51 +01:00
|
|
|
dbug.SetPhase(phase)
|
|
|
|
|
2015-02-13 18:05:09 +01:00
|
|
|
// Wait for no channel to exist
|
|
|
|
for c.runCh != nil {
|
|
|
|
c.l.Unlock()
|
|
|
|
ch := c.runCh
|
|
|
|
<-ch
|
|
|
|
c.l.Lock()
|
|
|
|
}
|
|
|
|
|
2016-10-02 01:51:00 +02:00
|
|
|
// Create the new channel
|
2015-02-13 18:05:09 +01:00
|
|
|
ch := make(chan struct{})
|
|
|
|
c.runCh = ch
|
2016-10-02 01:51:00 +02:00
|
|
|
|
2016-10-24 02:55:45 +02:00
|
|
|
// Reset the stop channel so we can watch that
|
|
|
|
c.stopCh = make(chan struct{})
|
|
|
|
|
2016-10-06 20:00:28 +02:00
|
|
|
// Reset the stop hook so we're not stopped
|
|
|
|
c.sh.Reset()
|
|
|
|
|
2016-10-02 01:51:00 +02:00
|
|
|
// Reset the shadow errors
|
|
|
|
c.shadowErr = nil
|
|
|
|
|
2015-02-13 18:05:09 +01:00
|
|
|
return ch
|
|
|
|
}
|
|
|
|
|
2015-02-14 03:15:36 +01:00
|
|
|
func (c *Context) releaseRun(ch chan<- struct{}) {
|
2015-02-13 18:05:09 +01:00
|
|
|
c.l.Lock()
|
|
|
|
defer c.l.Unlock()
|
|
|
|
|
2016-11-04 16:30:51 +01:00
|
|
|
// setting the phase to "INVALID" lets us easily detect if we have
|
|
|
|
// operations happening outside of a run, or we missed setting the proper
|
|
|
|
// phase
|
|
|
|
dbug.SetPhase("INVALID")
|
|
|
|
|
2015-02-13 18:05:09 +01:00
|
|
|
close(ch)
|
|
|
|
c.runCh = nil
|
2016-10-24 02:55:45 +02:00
|
|
|
c.stopCh = nil
|
2015-02-13 18:05:09 +01:00
|
|
|
}
|
|
|
|
|
2015-04-23 17:52:31 +02:00
|
|
|
func (c *Context) walk(
|
2016-10-02 01:51:00 +02:00
|
|
|
graph, shadow *Graph, operation walkOperation) (*ContextGraphWalker, error) {
|
|
|
|
// Keep track of the "real" context which is the context that does
|
|
|
|
// the real work: talking to real providers, modifying real state, etc.
|
|
|
|
realCtx := c
|
|
|
|
|
2016-10-26 21:46:22 +02:00
|
|
|
// If we don't want shadowing, remove it
|
|
|
|
if !experiment.Enabled(experiment.X_shadow) {
|
|
|
|
shadow = nil
|
|
|
|
}
|
|
|
|
|
2016-10-02 01:51:00 +02:00
|
|
|
// If we have a shadow graph, walk that as well
|
2016-10-05 05:52:14 +02:00
|
|
|
var shadowCtx *Context
|
2016-10-05 22:39:02 +02:00
|
|
|
var shadowCloser Shadow
|
2016-10-21 23:25:05 +02:00
|
|
|
if c.shadow && shadow != nil {
|
2016-10-02 01:51:00 +02:00
|
|
|
// Build the shadow context. In the process, override the real context
|
|
|
|
// with the one that is wrapped so that the shadow context can verify
|
|
|
|
// the results of the real.
|
|
|
|
realCtx, shadowCtx, shadowCloser = newShadowContext(c)
|
2016-10-06 19:49:52 +02:00
|
|
|
}
|
|
|
|
|
2016-10-21 23:25:05 +02:00
|
|
|
// Just log this so we can see it in a debug log
|
|
|
|
if !c.shadow {
|
|
|
|
log.Printf("[WARN] terraform: shadow graph disabled")
|
|
|
|
}
|
|
|
|
|
2016-10-06 19:49:52 +02:00
|
|
|
log.Printf("[DEBUG] Starting graph walk: %s", operation.String())
|
2016-11-04 16:30:51 +01:00
|
|
|
|
|
|
|
walker := &ContextGraphWalker{
|
2016-11-10 22:18:18 +01:00
|
|
|
Context: realCtx,
|
|
|
|
Operation: operation,
|
2016-11-04 16:30:51 +01:00
|
|
|
}
|
2016-10-02 01:51:00 +02:00
|
|
|
|
2016-10-24 02:55:45 +02:00
|
|
|
// Watch for a stop so we can call the provider Stop() API.
|
|
|
|
doneCh := make(chan struct{})
|
|
|
|
go c.watchStop(walker, c.stopCh, doneCh)
|
|
|
|
|
2016-10-06 19:49:52 +02:00
|
|
|
// Walk the real graph, this will block until it completes
|
|
|
|
realErr := graph.Walk(walker)
|
|
|
|
|
2016-10-24 02:55:45 +02:00
|
|
|
// Close the done channel so the watcher stops
|
|
|
|
close(doneCh)
|
|
|
|
|
2016-10-06 20:00:28 +02:00
|
|
|
// If we have a shadow graph and we interrupted the real graph, then
|
|
|
|
// we just close the shadow and never verify it. It is non-trivial to
|
|
|
|
// recreate the exact execution state up until an interruption so this
|
|
|
|
// isn't supported with shadows at the moment.
|
|
|
|
if shadowCloser != nil && c.sh.Stopped() {
|
|
|
|
// Ignore the error result, there is nothing we could care about
|
|
|
|
shadowCloser.CloseShadow()
|
|
|
|
|
|
|
|
// Set it to nil so we don't do anything
|
|
|
|
shadowCloser = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we have a shadow graph, wait for that to complete.
|
2016-10-06 19:49:52 +02:00
|
|
|
if shadowCloser != nil {
|
2016-11-03 20:09:51 +01:00
|
|
|
// Build the graph walker for the shadow. We also wrap this in
|
|
|
|
// a panicwrap so that panics are captured. For the shadow graph,
|
|
|
|
// we just want panics to be normal errors rather than to crash
|
|
|
|
// Terraform.
|
|
|
|
shadowWalker := GraphWalkerPanicwrap(&ContextGraphWalker{
|
2016-11-10 22:18:18 +01:00
|
|
|
Context: shadowCtx,
|
|
|
|
Operation: operation,
|
2016-11-03 20:09:51 +01:00
|
|
|
})
|
2016-10-02 01:51:00 +02:00
|
|
|
|
|
|
|
// Kick off the shadow walk. This will block on any operations
|
|
|
|
// on the real walk so it is fine to start first.
|
2016-10-22 01:20:38 +02:00
|
|
|
log.Printf("[INFO] Starting shadow graph walk: %s", operation.String())
|
2016-10-06 19:49:52 +02:00
|
|
|
shadowCh := make(chan error)
|
2016-10-02 01:51:00 +02:00
|
|
|
go func() {
|
|
|
|
shadowCh <- shadow.Walk(shadowWalker)
|
|
|
|
}()
|
|
|
|
|
|
|
|
// Notify the shadow that we're done
|
2016-10-05 22:39:02 +02:00
|
|
|
if err := shadowCloser.CloseShadow(); err != nil {
|
2016-10-02 01:51:00 +02:00
|
|
|
c.shadowErr = multierror.Append(c.shadowErr, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Wait for the walk to end
|
2016-10-03 18:45:18 +02:00
|
|
|
log.Printf("[DEBUG] Waiting for shadow graph to complete...")
|
2016-10-05 22:39:02 +02:00
|
|
|
shadowWalkErr := <-shadowCh
|
|
|
|
|
|
|
|
// Get any shadow errors
|
|
|
|
if err := shadowCloser.ShadowError(); err != nil {
|
2016-10-02 01:51:00 +02:00
|
|
|
c.shadowErr = multierror.Append(c.shadowErr, err)
|
|
|
|
}
|
|
|
|
|
2016-10-05 05:52:14 +02:00
|
|
|
// Verify the contexts (compare)
|
|
|
|
if err := shadowContextVerify(realCtx, shadowCtx); err != nil {
|
|
|
|
c.shadowErr = multierror.Append(c.shadowErr, err)
|
|
|
|
}
|
|
|
|
|
2016-10-05 22:39:02 +02:00
|
|
|
// At this point, if we're supposed to fail on error, then
|
|
|
|
// we PANIC. Some tests just verify that there is an error,
|
|
|
|
// so simply appending it to realErr and returning could hide
|
|
|
|
// shadow problems.
|
|
|
|
//
|
|
|
|
// This must be done BEFORE appending shadowWalkErr since the
|
|
|
|
// shadowWalkErr may include expected errors.
|
2016-11-08 17:32:36 +01:00
|
|
|
//
|
|
|
|
// We only do this if we don't have a real error. In the case of
|
|
|
|
// a real error, we can't guarantee what nodes were and weren't
|
|
|
|
// traversed in parallel scenarios so we can't guarantee no
|
|
|
|
// shadow errors.
|
|
|
|
if c.shadowErr != nil && contextFailOnShadowError && realErr == nil {
|
2016-10-05 22:39:02 +02:00
|
|
|
panic(multierror.Prefix(c.shadowErr, "shadow graph:"))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Now, if we have a walk error, we append that through
|
|
|
|
if shadowWalkErr != nil {
|
|
|
|
c.shadowErr = multierror.Append(c.shadowErr, shadowWalkErr)
|
|
|
|
}
|
|
|
|
|
2016-10-03 18:45:18 +02:00
|
|
|
if c.shadowErr == nil {
|
|
|
|
log.Printf("[INFO] Shadow graph success!")
|
|
|
|
} else {
|
|
|
|
log.Printf("[ERROR] Shadow graph error: %s", c.shadowErr)
|
|
|
|
|
|
|
|
// If we're supposed to fail on shadow errors, then report it
|
|
|
|
if contextFailOnShadowError {
|
|
|
|
realErr = multierror.Append(realErr, multierror.Prefix(
|
|
|
|
c.shadowErr, "shadow graph:"))
|
|
|
|
}
|
2016-10-02 01:51:00 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return walker, realErr
|
2015-02-10 17:58:14 +01:00
|
|
|
}
|
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-21 03:38:26 +02:00
|
|
|
|
2016-10-24 02:55:45 +02:00
|
|
|
func (c *Context) watchStop(walker *ContextGraphWalker, stopCh, doneCh <-chan struct{}) {
|
|
|
|
// Wait for a stop or completion
|
|
|
|
select {
|
|
|
|
case <-stopCh:
|
|
|
|
// Stop was triggered. Fall out of the select
|
|
|
|
case <-doneCh:
|
|
|
|
// Done, just exit completely
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we're here, we're stopped, trigger the call.
|
|
|
|
|
|
|
|
// Copy the providers so that a misbehaved blocking Stop doesn't
|
|
|
|
// completely hang Terraform.
|
|
|
|
walker.providerLock.Lock()
|
|
|
|
ps := make([]ResourceProvider, 0, len(walker.providerCache))
|
|
|
|
for _, p := range walker.providerCache {
|
|
|
|
ps = append(ps, p)
|
|
|
|
}
|
|
|
|
defer walker.providerLock.Unlock()
|
|
|
|
|
|
|
|
for _, p := range ps {
|
|
|
|
// We ignore the error for now since there isn't any reasonable
|
|
|
|
// action to take if there is an error here, since the stop is still
|
|
|
|
// advisory: Terraform will exit once the graph node completes.
|
|
|
|
p.Stop()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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-21 03:38:26 +02:00
|
|
|
// parseVariableAsHCL parses the value of a single variable as would have been specified
|
|
|
|
// on the command line via -var or in an environment variable named TF_VAR_x, where x is
|
|
|
|
// the name of the variable. In order to get around the restriction of HCL requiring a
|
|
|
|
// top level object, we prepend a sentinel key, decode the user-specified value as its
|
|
|
|
// value and pull the value back out of the resulting map.
|
2016-08-09 22:14:40 +02:00
|
|
|
func parseVariableAsHCL(name string, input string, targetType config.VariableType) (interface{}, error) {
|
|
|
|
// expecting a string so don't decode anything, just strip quotes
|
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-21 03:38:26 +02:00
|
|
|
if targetType == config.VariableTypeString {
|
2016-08-09 22:14:40 +02:00
|
|
|
return strings.Trim(input, `"`), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// return empty types
|
|
|
|
if strings.TrimSpace(input) == "" {
|
|
|
|
switch targetType {
|
|
|
|
case config.VariableTypeList:
|
|
|
|
return []interface{}{}, nil
|
|
|
|
case config.VariableTypeMap:
|
|
|
|
return make(map[string]interface{}), nil
|
|
|
|
}
|
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-21 03:38:26 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
const sentinelValue = "SENTINEL_TERRAFORM_VAR_OVERRIDE_KEY"
|
|
|
|
inputWithSentinal := fmt.Sprintf("%s = %s", sentinelValue, input)
|
|
|
|
|
|
|
|
var decoded map[string]interface{}
|
|
|
|
err := hcl.Decode(&decoded, inputWithSentinal)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("Cannot parse value for variable %s (%q) as valid HCL: %s", name, input, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(decoded) != 1 {
|
|
|
|
return nil, fmt.Errorf("Cannot parse value for variable %s (%q) as valid HCL. Only one value may be specified.", name, input)
|
|
|
|
}
|
|
|
|
|
|
|
|
parsedValue, ok := decoded[sentinelValue]
|
|
|
|
if !ok {
|
|
|
|
return nil, fmt.Errorf("Cannot parse value for variable %s (%q) as valid HCL. One value must be specified.", name, input)
|
|
|
|
}
|
|
|
|
|
|
|
|
switch targetType {
|
|
|
|
case config.VariableTypeList:
|
|
|
|
return parsedValue, nil
|
|
|
|
case config.VariableTypeMap:
|
|
|
|
if list, ok := parsedValue.([]map[string]interface{}); ok {
|
|
|
|
return list[0], nil
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, fmt.Errorf("Cannot parse value for variable %s (%q) as valid HCL. One value must be specified.", name, input)
|
|
|
|
default:
|
2016-07-29 22:05:57 +02:00
|
|
|
panic(fmt.Errorf("unknown type %s", targetType.Printable()))
|
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-21 03:38:26 +02:00
|
|
|
}
|
|
|
|
}
|