mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-25 08:21:07 -06:00
f8c7b639c9
This switches to the Go "context" package for cancellation and threads the context through all the way to evaluation to allow behavior based on stopping deep within graph execution. This also adds the Stop API to provisioners so they can quickly exit when stop is called.
157 lines
4.4 KiB
Go
157 lines
4.4 KiB
Go
package terraform
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/hashicorp/go-multierror"
|
|
"github.com/mitchellh/copystructure"
|
|
)
|
|
|
|
// newShadowContext creates a new context that will shadow the given context
|
|
// when walking the graph. The resulting context should be used _only once_
|
|
// for a graph walk.
|
|
//
|
|
// The returned Shadow should be closed after the graph walk with the
|
|
// real context is complete. Errors from the shadow can be retrieved there.
|
|
//
|
|
// Most importantly, any operations done on the shadow context (the returned
|
|
// context) will NEVER affect the real context. All structures are deep
|
|
// copied, no real providers or resources are used, etc.
|
|
func newShadowContext(c *Context) (*Context, *Context, Shadow) {
|
|
// Copy the targets
|
|
targetRaw, err := copystructure.Copy(c.targets)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// Copy the variables
|
|
varRaw, err := copystructure.Copy(c.variables)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// Copy the provider inputs
|
|
providerInputRaw, err := copystructure.Copy(c.providerInputConfig)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
// The factories
|
|
componentsReal, componentsShadow := newShadowComponentFactory(c.components)
|
|
|
|
// Create the shadow
|
|
shadow := &Context{
|
|
components: componentsShadow,
|
|
destroy: c.destroy,
|
|
diff: c.diff.DeepCopy(),
|
|
hooks: nil,
|
|
module: c.module,
|
|
state: c.state.DeepCopy(),
|
|
targets: targetRaw.([]string),
|
|
variables: varRaw.(map[string]interface{}),
|
|
|
|
// NOTE(mitchellh): This is not going to work for shadows that are
|
|
// testing that input results in the proper end state. At the time
|
|
// of writing, input is not used in any state-changing graph
|
|
// walks anyways, so this checks nothing. We set it to this to avoid
|
|
// any panics but even a "nil" value worked here.
|
|
uiInput: new(MockUIInput),
|
|
|
|
// Hardcoded to 4 since parallelism in the shadow doesn't matter
|
|
// a ton since we're doing far less compared to the real side
|
|
// and our operations are MUCH faster.
|
|
parallelSem: NewSemaphore(4),
|
|
providerInputConfig: providerInputRaw.(map[string]map[string]interface{}),
|
|
}
|
|
|
|
// Create the real context. This is effectively just a copy of
|
|
// the context given except we need to modify some of the values
|
|
// to point to the real side of a shadow so the shadow can compare values.
|
|
real := &Context{
|
|
// The fields below are changed.
|
|
components: componentsReal,
|
|
|
|
// The fields below are direct copies
|
|
destroy: c.destroy,
|
|
diff: c.diff,
|
|
// diffLock - no copy
|
|
hooks: c.hooks,
|
|
module: c.module,
|
|
sh: c.sh,
|
|
state: c.state,
|
|
// stateLock - no copy
|
|
targets: c.targets,
|
|
uiInput: c.uiInput,
|
|
variables: c.variables,
|
|
|
|
// l - no copy
|
|
parallelSem: c.parallelSem,
|
|
providerInputConfig: c.providerInputConfig,
|
|
runContext: c.runContext,
|
|
runContextCancel: c.runContextCancel,
|
|
shadowErr: c.shadowErr,
|
|
}
|
|
|
|
return real, shadow, &shadowContextCloser{
|
|
Components: componentsShadow,
|
|
}
|
|
}
|
|
|
|
// shadowContextVerify takes the real and shadow context and verifies they
|
|
// have equal diffs and states.
|
|
func shadowContextVerify(real, shadow *Context) error {
|
|
var result error
|
|
|
|
// The states compared must be pruned so they're minimal/clean
|
|
real.state.prune()
|
|
shadow.state.prune()
|
|
|
|
// Compare the states
|
|
if !real.state.Equal(shadow.state) {
|
|
result = multierror.Append(result, fmt.Errorf(
|
|
"Real and shadow states do not match! "+
|
|
"Real state:\n\n%s\n\n"+
|
|
"Shadow state:\n\n%s\n\n",
|
|
real.state, shadow.state))
|
|
}
|
|
|
|
// Compare the diffs
|
|
if !real.diff.Equal(shadow.diff) {
|
|
result = multierror.Append(result, fmt.Errorf(
|
|
"Real and shadow diffs do not match! "+
|
|
"Real diff:\n\n%s\n\n"+
|
|
"Shadow diff:\n\n%s\n\n",
|
|
real.diff, shadow.diff))
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
// shadowContextCloser is the io.Closer returned by newShadowContext that
|
|
// closes all the shadows and returns the results.
|
|
type shadowContextCloser struct {
|
|
Components *shadowComponentFactory
|
|
}
|
|
|
|
// Close closes the shadow context.
|
|
func (c *shadowContextCloser) CloseShadow() error {
|
|
return c.Components.CloseShadow()
|
|
}
|
|
|
|
func (c *shadowContextCloser) ShadowError() error {
|
|
err := c.Components.ShadowError()
|
|
if err == nil {
|
|
return nil
|
|
}
|
|
|
|
// This is a sad edge case: if the configuration contains uuid() at
|
|
// any point, we cannot reason aboyt the shadow execution. Tested
|
|
// with Context2Plan_shadowUuid.
|
|
if strings.Contains(err.Error(), "uuid()") {
|
|
err = nil
|
|
}
|
|
|
|
return err
|
|
}
|