2017-01-19 05:47:56 +01:00
|
|
|
package local
|
|
|
|
|
|
|
|
import (
|
2018-02-23 02:43:21 +01:00
|
|
|
"context"
|
2017-06-22 19:15:30 +02:00
|
|
|
"errors"
|
2017-02-07 22:22:28 +01:00
|
|
|
"log"
|
2017-11-22 00:08:00 +01:00
|
|
|
|
2017-01-19 05:47:56 +01:00
|
|
|
"github.com/hashicorp/errwrap"
|
|
|
|
"github.com/hashicorp/terraform/backend"
|
2018-07-04 12:11:35 +02:00
|
|
|
"github.com/hashicorp/terraform/command/clistate"
|
|
|
|
"github.com/hashicorp/terraform/command/format"
|
2017-01-19 05:47:56 +01:00
|
|
|
"github.com/hashicorp/terraform/state"
|
|
|
|
"github.com/hashicorp/terraform/terraform"
|
2018-07-04 12:11:35 +02:00
|
|
|
"github.com/hashicorp/terraform/tfdiags"
|
2017-01-19 05:47:56 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
// backend.Local implementation.
|
|
|
|
func (b *Local) Context(op *backend.Operation) (*terraform.Context, state.State, error) {
|
|
|
|
// Make sure the type is invalid. We use this as a way to know not
|
|
|
|
// to ask for input/validate.
|
|
|
|
op.Type = backend.OperationTypeInvalid
|
|
|
|
|
2018-02-23 02:43:21 +01:00
|
|
|
if op.LockState {
|
|
|
|
op.StateLocker = clistate.NewLocker(context.Background(), op.StateLockTimeout, b.CLI, b.Colorize())
|
|
|
|
} else {
|
|
|
|
op.StateLocker = clistate.NewNoopLocker()
|
|
|
|
}
|
|
|
|
|
2017-01-19 05:47:56 +01:00
|
|
|
return b.context(op)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (b *Local) context(op *backend.Operation) (*terraform.Context, state.State, error) {
|
|
|
|
// Get the state.
|
2017-05-31 18:37:31 +02:00
|
|
|
s, err := b.State(op.Workspace)
|
2017-01-19 05:47:56 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, errwrap.Wrapf("Error loading state: {{err}}", err)
|
|
|
|
}
|
2017-02-02 00:16:16 +01:00
|
|
|
|
2018-02-23 02:43:21 +01:00
|
|
|
if err := op.StateLocker.Lock(s, op.Type.String()); err != nil {
|
|
|
|
return nil, nil, errwrap.Wrapf("Error locking state: {{err}}", err)
|
|
|
|
}
|
|
|
|
|
2017-01-19 05:47:56 +01:00
|
|
|
if err := s.RefreshState(); err != nil {
|
|
|
|
return nil, nil, errwrap.Wrapf("Error loading state: {{err}}", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Initialize our context options
|
|
|
|
var opts terraform.ContextOpts
|
|
|
|
if v := b.ContextOpts; v != nil {
|
|
|
|
opts = *v
|
|
|
|
}
|
|
|
|
|
|
|
|
// Copy set options from the operation
|
|
|
|
opts.Destroy = op.Destroy
|
|
|
|
opts.Module = op.Module
|
|
|
|
opts.Targets = op.Targets
|
|
|
|
opts.UIInput = op.UIIn
|
|
|
|
if op.Variables != nil {
|
|
|
|
opts.Variables = op.Variables
|
|
|
|
}
|
|
|
|
|
|
|
|
// Load our state
|
2017-07-17 16:35:48 +02:00
|
|
|
// By the time we get here, the backend creation code in "command" took
|
|
|
|
// care of making s.State() return a state compatible with our plan,
|
|
|
|
// if any, so we can safely pass this value in both the plan context
|
|
|
|
// and new context cases below.
|
2017-01-19 05:47:56 +01:00
|
|
|
opts.State = s.State()
|
|
|
|
|
|
|
|
// Build the context
|
|
|
|
var tfCtx *terraform.Context
|
|
|
|
if op.Plan != nil {
|
|
|
|
tfCtx, err = op.Plan.Context(&opts)
|
|
|
|
} else {
|
|
|
|
tfCtx, err = terraform.NewContext(&opts)
|
|
|
|
}
|
2017-06-22 19:15:30 +02:00
|
|
|
|
|
|
|
// any errors resolving plugins returns this
|
|
|
|
if rpe, ok := err.(*terraform.ResourceProviderError); ok {
|
|
|
|
b.pluginInitRequired(rpe)
|
|
|
|
// we wrote the full UI error here, so return a generic error for flow
|
|
|
|
// control in the command.
|
|
|
|
return nil, nil, errors.New("error satisfying plugin requirements")
|
|
|
|
}
|
|
|
|
|
2017-01-19 05:47:56 +01:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we have an operation, then we automatically do the input/validate
|
|
|
|
// here since every option requires this.
|
|
|
|
if op.Type != backend.OperationTypeInvalid {
|
|
|
|
// If input asking is enabled, then do that
|
|
|
|
if op.Plan == nil && b.OpInput {
|
|
|
|
mode := terraform.InputModeProvider
|
|
|
|
mode |= terraform.InputModeVar
|
|
|
|
mode |= terraform.InputModeVarUnset
|
|
|
|
|
|
|
|
if err := tfCtx.Input(mode); err != nil {
|
|
|
|
return nil, nil, errwrap.Wrapf("Error asking for user input: {{err}}", err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If validation is enabled, validate
|
|
|
|
if b.OpValidation {
|
2017-11-22 00:08:00 +01:00
|
|
|
diags := tfCtx.Validate()
|
|
|
|
if len(diags) > 0 {
|
|
|
|
if diags.HasErrors() {
|
|
|
|
// If there are warnings _and_ errors then we'll take this
|
|
|
|
// path and return them all together in this error.
|
|
|
|
return nil, nil, diags.Err()
|
|
|
|
}
|
2017-02-07 22:22:28 +01:00
|
|
|
|
2017-11-22 00:08:00 +01:00
|
|
|
// For now we can't propagate warnings any further without
|
|
|
|
// printing them directly to the UI, so we'll need to
|
|
|
|
// format them here ourselves.
|
|
|
|
for _, diag := range diags {
|
|
|
|
if diag.Severity() != tfdiags.Warning {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
if b.CLI != nil {
|
|
|
|
b.CLI.Warn(format.Diagnostic(diag, b.Colorize(), 72))
|
|
|
|
} else {
|
|
|
|
desc := diag.Description()
|
|
|
|
log.Printf("[WARN] backend/local: %s", desc.Summary)
|
|
|
|
}
|
2017-02-07 22:22:28 +01:00
|
|
|
}
|
|
|
|
|
2017-11-22 00:08:00 +01:00
|
|
|
// Make a newline before continuing
|
|
|
|
b.CLI.Output("")
|
2017-01-19 05:47:56 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return tfCtx, s, nil
|
|
|
|
}
|
2017-02-07 22:22:28 +01:00
|
|
|
|
|
|
|
const validateWarnHeader = `
|
|
|
|
There are warnings related to your configuration. If no errors occurred,
|
|
|
|
Terraform will continue despite these warnings. It is a good idea to resolve
|
|
|
|
these warnings in the near future.
|
|
|
|
|
|
|
|
Warnings:
|
|
|
|
`
|