opentofu/terraform/shadow_resource_provider.go
Mitchell Hashimoto c6fd938fb8
terraform: EvalInstanceInfo on data sources in new graph
This doesn't cause any practical issues as far as I'm aware (couldn't
get any test to fail), but caused shadow errors since it wasn't matching
the prior behavior.
2016-11-15 09:02:10 -08:00

816 lines
21 KiB
Go

package terraform
import (
"fmt"
"log"
"sync"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/terraform/helper/shadow"
)
// shadowResourceProvider implements ResourceProvider for the shadow
// eval context defined in eval_context_shadow.go.
//
// This is used to verify behavior with a real provider. This shouldn't
// be used directly.
type shadowResourceProvider interface {
ResourceProvider
Shadow
}
// newShadowResourceProvider creates a new shadowed ResourceProvider.
//
// This will assume a well behaved real ResourceProvider. For example,
// it assumes that the `Resources` call underneath doesn't change values
// since once it is called on the real provider, it will be cached and
// returned in the shadow since number of calls to that shouldn't affect
// actual behavior.
//
// However, with calls like Apply, call order is taken into account,
// parameters are checked for equality, etc.
func newShadowResourceProvider(p ResourceProvider) (ResourceProvider, shadowResourceProvider) {
// Create the shared data
shared := shadowResourceProviderShared{}
// Create the real provider that does actual work
real := &shadowResourceProviderReal{
ResourceProvider: p,
Shared: &shared,
}
// Create the shadow that watches the real value
shadow := &shadowResourceProviderShadow{
Shared: &shared,
resources: p.Resources(),
dataSources: p.DataSources(),
}
return real, shadow
}
// shadowResourceProviderReal is the real resource provider. Function calls
// to this will perform real work. This records the parameters and return
// values and call order for the shadow to reproduce.
type shadowResourceProviderReal struct {
ResourceProvider
Shared *shadowResourceProviderShared
}
func (p *shadowResourceProviderReal) Close() error {
var result error
if c, ok := p.ResourceProvider.(ResourceProviderCloser); ok {
result = c.Close()
}
p.Shared.CloseErr.SetValue(result)
return result
}
func (p *shadowResourceProviderReal) Input(
input UIInput, c *ResourceConfig) (*ResourceConfig, error) {
cCopy := c.DeepCopy()
result, err := p.ResourceProvider.Input(input, c)
p.Shared.Input.SetValue(&shadowResourceProviderInput{
Config: cCopy,
Result: result.DeepCopy(),
ResultErr: err,
})
return result, err
}
func (p *shadowResourceProviderReal) Validate(c *ResourceConfig) ([]string, []error) {
warns, errs := p.ResourceProvider.Validate(c)
p.Shared.Validate.SetValue(&shadowResourceProviderValidate{
Config: c.DeepCopy(),
ResultWarn: warns,
ResultErr: errs,
})
return warns, errs
}
func (p *shadowResourceProviderReal) Configure(c *ResourceConfig) error {
cCopy := c.DeepCopy()
err := p.ResourceProvider.Configure(c)
p.Shared.Configure.SetValue(&shadowResourceProviderConfigure{
Config: cCopy,
Result: err,
})
return err
}
func (p *shadowResourceProviderReal) Stop() error {
return p.ResourceProvider.Stop()
}
func (p *shadowResourceProviderReal) ValidateResource(
t string, c *ResourceConfig) ([]string, []error) {
key := t
configCopy := c.DeepCopy()
// Real operation
warns, errs := p.ResourceProvider.ValidateResource(t, c)
// Initialize to ensure we always have a wrapper with a lock
p.Shared.ValidateResource.Init(
key, &shadowResourceProviderValidateResourceWrapper{})
// Get the result
raw := p.Shared.ValidateResource.Value(key)
wrapper, ok := raw.(*shadowResourceProviderValidateResourceWrapper)
if !ok {
// If this fails then we just continue with our day... the shadow
// will fail to but there isn't much we can do.
log.Printf(
"[ERROR] unknown value in ValidateResource shadow value: %#v", raw)
return warns, errs
}
// Lock the wrapper for writing and record our call
wrapper.Lock()
defer wrapper.Unlock()
wrapper.Calls = append(wrapper.Calls, &shadowResourceProviderValidateResource{
Config: configCopy,
Warns: warns,
Errors: errs,
})
// With it locked, call SetValue again so that it triggers WaitForChange
p.Shared.ValidateResource.SetValue(key, wrapper)
// Return the result
return warns, errs
}
func (p *shadowResourceProviderReal) Apply(
info *InstanceInfo,
state *InstanceState,
diff *InstanceDiff) (*InstanceState, error) {
// Thse have to be copied before the call since call can modify
stateCopy := state.DeepCopy()
diffCopy := diff.DeepCopy()
result, err := p.ResourceProvider.Apply(info, state, diff)
p.Shared.Apply.SetValue(info.uniqueId(), &shadowResourceProviderApply{
State: stateCopy,
Diff: diffCopy,
Result: result.DeepCopy(),
ResultErr: err,
})
return result, err
}
func (p *shadowResourceProviderReal) Diff(
info *InstanceInfo,
state *InstanceState,
desired *ResourceConfig) (*InstanceDiff, error) {
// Thse have to be copied before the call since call can modify
stateCopy := state.DeepCopy()
desiredCopy := desired.DeepCopy()
result, err := p.ResourceProvider.Diff(info, state, desired)
p.Shared.Diff.SetValue(info.uniqueId(), &shadowResourceProviderDiff{
State: stateCopy,
Desired: desiredCopy,
Result: result.DeepCopy(),
ResultErr: err,
})
return result, err
}
func (p *shadowResourceProviderReal) Refresh(
info *InstanceInfo,
state *InstanceState) (*InstanceState, error) {
// Thse have to be copied before the call since call can modify
stateCopy := state.DeepCopy()
result, err := p.ResourceProvider.Refresh(info, state)
p.Shared.Refresh.SetValue(info.uniqueId(), &shadowResourceProviderRefresh{
State: stateCopy,
Result: result.DeepCopy(),
ResultErr: err,
})
return result, err
}
func (p *shadowResourceProviderReal) ValidateDataSource(
t string, c *ResourceConfig) ([]string, []error) {
key := t
configCopy := c.DeepCopy()
// Real operation
warns, errs := p.ResourceProvider.ValidateDataSource(t, c)
// Initialize
p.Shared.ValidateDataSource.Init(
key, &shadowResourceProviderValidateDataSourceWrapper{})
// Get the result
raw := p.Shared.ValidateDataSource.Value(key)
wrapper, ok := raw.(*shadowResourceProviderValidateDataSourceWrapper)
if !ok {
// If this fails then we just continue with our day... the shadow
// will fail to but there isn't much we can do.
log.Printf(
"[ERROR] unknown value in ValidateDataSource shadow value: %#v", raw)
return warns, errs
}
// Lock the wrapper for writing and record our call
wrapper.Lock()
defer wrapper.Unlock()
wrapper.Calls = append(wrapper.Calls, &shadowResourceProviderValidateDataSource{
Config: configCopy,
Warns: warns,
Errors: errs,
})
// Set it
p.Shared.ValidateDataSource.SetValue(key, wrapper)
// Return the result
return warns, errs
}
func (p *shadowResourceProviderReal) ReadDataDiff(
info *InstanceInfo,
desired *ResourceConfig) (*InstanceDiff, error) {
// These have to be copied before the call since call can modify
desiredCopy := desired.DeepCopy()
result, err := p.ResourceProvider.ReadDataDiff(info, desired)
p.Shared.ReadDataDiff.SetValue(info.uniqueId(), &shadowResourceProviderReadDataDiff{
Desired: desiredCopy,
Result: result.DeepCopy(),
ResultErr: err,
})
return result, err
}
func (p *shadowResourceProviderReal) ReadDataApply(
info *InstanceInfo,
diff *InstanceDiff) (*InstanceState, error) {
// Thse have to be copied before the call since call can modify
diffCopy := diff.DeepCopy()
result, err := p.ResourceProvider.ReadDataApply(info, diff)
p.Shared.ReadDataApply.SetValue(info.uniqueId(), &shadowResourceProviderReadDataApply{
Diff: diffCopy,
Result: result.DeepCopy(),
ResultErr: err,
})
return result, err
}
// shadowResourceProviderShadow is the shadow resource provider. Function
// calls never affect real resources. This is paired with the "real" side
// which must be called properly to enable recording.
type shadowResourceProviderShadow struct {
Shared *shadowResourceProviderShared
// Cached values that are expected to not change
resources []ResourceType
dataSources []DataSource
Error error // Error is the list of errors from the shadow
ErrorLock sync.Mutex
}
type shadowResourceProviderShared struct {
// NOTE: Anytime a value is added here, be sure to add it to
// the Close() method so that it is closed.
CloseErr shadow.Value
Input shadow.Value
Validate shadow.Value
Configure shadow.Value
ValidateResource shadow.KeyedValue
Apply shadow.KeyedValue
Diff shadow.KeyedValue
Refresh shadow.KeyedValue
ValidateDataSource shadow.KeyedValue
ReadDataDiff shadow.KeyedValue
ReadDataApply shadow.KeyedValue
}
func (p *shadowResourceProviderShared) Close() error {
return shadow.Close(p)
}
func (p *shadowResourceProviderShadow) CloseShadow() error {
err := p.Shared.Close()
if err != nil {
err = fmt.Errorf("close error: %s", err)
}
return err
}
func (p *shadowResourceProviderShadow) ShadowError() error {
return p.Error
}
func (p *shadowResourceProviderShadow) Resources() []ResourceType {
return p.resources
}
func (p *shadowResourceProviderShadow) DataSources() []DataSource {
return p.dataSources
}
func (p *shadowResourceProviderShadow) Close() error {
v := p.Shared.CloseErr.Value()
if v == nil {
return nil
}
return v.(error)
}
func (p *shadowResourceProviderShadow) Input(
input UIInput, c *ResourceConfig) (*ResourceConfig, error) {
// Get the result of the input call
raw := p.Shared.Input.Value()
if raw == nil {
return nil, nil
}
result, ok := raw.(*shadowResourceProviderInput)
if !ok {
p.ErrorLock.Lock()
defer p.ErrorLock.Unlock()
p.Error = multierror.Append(p.Error, fmt.Errorf(
"Unknown 'input' shadow value: %#v", raw))
return nil, nil
}
// Compare the parameters, which should be identical
if !c.Equal(result.Config) {
p.ErrorLock.Lock()
p.Error = multierror.Append(p.Error, fmt.Errorf(
"Input had unequal configurations (real, then shadow):\n\n%#v\n\n%#v",
result.Config, c))
p.ErrorLock.Unlock()
}
// Return the results
return result.Result, result.ResultErr
}
func (p *shadowResourceProviderShadow) Validate(c *ResourceConfig) ([]string, []error) {
// Get the result of the validate call
raw := p.Shared.Validate.Value()
if raw == nil {
return nil, nil
}
result, ok := raw.(*shadowResourceProviderValidate)
if !ok {
p.ErrorLock.Lock()
defer p.ErrorLock.Unlock()
p.Error = multierror.Append(p.Error, fmt.Errorf(
"Unknown 'validate' shadow value: %#v", raw))
return nil, nil
}
// Compare the parameters, which should be identical
if !c.Equal(result.Config) {
p.ErrorLock.Lock()
p.Error = multierror.Append(p.Error, fmt.Errorf(
"Validate had unequal configurations (real, then shadow):\n\n%#v\n\n%#v",
result.Config, c))
p.ErrorLock.Unlock()
}
// Return the results
return result.ResultWarn, result.ResultErr
}
func (p *shadowResourceProviderShadow) Configure(c *ResourceConfig) error {
// Get the result of the call
raw := p.Shared.Configure.Value()
if raw == nil {
return nil
}
result, ok := raw.(*shadowResourceProviderConfigure)
if !ok {
p.ErrorLock.Lock()
defer p.ErrorLock.Unlock()
p.Error = multierror.Append(p.Error, fmt.Errorf(
"Unknown 'configure' shadow value: %#v", raw))
return nil
}
// Compare the parameters, which should be identical
if !c.Equal(result.Config) {
p.ErrorLock.Lock()
p.Error = multierror.Append(p.Error, fmt.Errorf(
"Configure had unequal configurations (real, then shadow):\n\n%#v\n\n%#v",
result.Config, c))
p.ErrorLock.Unlock()
}
// Return the results
return result.Result
}
// Stop returns immediately.
func (p *shadowResourceProviderShadow) Stop() error {
return nil
}
func (p *shadowResourceProviderShadow) ValidateResource(t string, c *ResourceConfig) ([]string, []error) {
// Unique key
key := t
// Get the initial value
raw := p.Shared.ValidateResource.Value(key)
// Find a validation with our configuration
var result *shadowResourceProviderValidateResource
for {
// Get the value
if raw == nil {
p.ErrorLock.Lock()
defer p.ErrorLock.Unlock()
p.Error = multierror.Append(p.Error, fmt.Errorf(
"Unknown 'ValidateResource' call for %q:\n\n%#v",
key, c))
return nil, nil
}
wrapper, ok := raw.(*shadowResourceProviderValidateResourceWrapper)
if !ok {
p.ErrorLock.Lock()
defer p.ErrorLock.Unlock()
p.Error = multierror.Append(p.Error, fmt.Errorf(
"Unknown 'ValidateResource' shadow value for %q: %#v", key, raw))
return nil, nil
}
// Look for the matching call with our configuration
wrapper.RLock()
for _, call := range wrapper.Calls {
if call.Config.Equal(c) {
result = call
break
}
}
wrapper.RUnlock()
// If we found a result, exit
if result != nil {
break
}
// Wait for a change so we can get the wrapper again
raw = p.Shared.ValidateResource.WaitForChange(key)
}
return result.Warns, result.Errors
}
func (p *shadowResourceProviderShadow) Apply(
info *InstanceInfo,
state *InstanceState,
diff *InstanceDiff) (*InstanceState, error) {
// Unique key
key := info.uniqueId()
raw := p.Shared.Apply.Value(key)
if raw == nil {
p.ErrorLock.Lock()
defer p.ErrorLock.Unlock()
p.Error = multierror.Append(p.Error, fmt.Errorf(
"Unknown 'apply' call for %q:\n\n%#v\n\n%#v",
key, state, diff))
return nil, nil
}
result, ok := raw.(*shadowResourceProviderApply)
if !ok {
p.ErrorLock.Lock()
defer p.ErrorLock.Unlock()
p.Error = multierror.Append(p.Error, fmt.Errorf(
"Unknown 'apply' shadow value for %q: %#v", key, raw))
return nil, nil
}
// Compare the parameters, which should be identical
if !state.Equal(result.State) {
p.ErrorLock.Lock()
p.Error = multierror.Append(p.Error, fmt.Errorf(
"Apply %q: state had unequal states (real, then shadow):\n\n%#v\n\n%#v",
key, result.State, state))
p.ErrorLock.Unlock()
}
if !diff.Equal(result.Diff) {
p.ErrorLock.Lock()
p.Error = multierror.Append(p.Error, fmt.Errorf(
"Apply %q: unequal diffs (real, then shadow):\n\n%#v\n\n%#v",
key, result.Diff, diff))
p.ErrorLock.Unlock()
}
return result.Result, result.ResultErr
}
func (p *shadowResourceProviderShadow) Diff(
info *InstanceInfo,
state *InstanceState,
desired *ResourceConfig) (*InstanceDiff, error) {
// Unique key
key := info.uniqueId()
raw := p.Shared.Diff.Value(key)
if raw == nil {
p.ErrorLock.Lock()
defer p.ErrorLock.Unlock()
p.Error = multierror.Append(p.Error, fmt.Errorf(
"Unknown 'diff' call for %q:\n\n%#v\n\n%#v",
key, state, desired))
return nil, nil
}
result, ok := raw.(*shadowResourceProviderDiff)
if !ok {
p.ErrorLock.Lock()
defer p.ErrorLock.Unlock()
p.Error = multierror.Append(p.Error, fmt.Errorf(
"Unknown 'diff' shadow value for %q: %#v", key, raw))
return nil, nil
}
// Compare the parameters, which should be identical
if !state.Equal(result.State) {
p.ErrorLock.Lock()
p.Error = multierror.Append(p.Error, fmt.Errorf(
"Diff %q had unequal states (real, then shadow):\n\n%#v\n\n%#v",
key, result.State, state))
p.ErrorLock.Unlock()
}
if !desired.Equal(result.Desired) {
p.ErrorLock.Lock()
p.Error = multierror.Append(p.Error, fmt.Errorf(
"Diff %q had unequal states (real, then shadow):\n\n%#v\n\n%#v",
key, result.Desired, desired))
p.ErrorLock.Unlock()
}
return result.Result, result.ResultErr
}
func (p *shadowResourceProviderShadow) Refresh(
info *InstanceInfo,
state *InstanceState) (*InstanceState, error) {
// Unique key
key := info.uniqueId()
raw := p.Shared.Refresh.Value(key)
if raw == nil {
p.ErrorLock.Lock()
defer p.ErrorLock.Unlock()
p.Error = multierror.Append(p.Error, fmt.Errorf(
"Unknown 'refresh' call for %q:\n\n%#v",
key, state))
return nil, nil
}
result, ok := raw.(*shadowResourceProviderRefresh)
if !ok {
p.ErrorLock.Lock()
defer p.ErrorLock.Unlock()
p.Error = multierror.Append(p.Error, fmt.Errorf(
"Unknown 'refresh' shadow value: %#v", raw))
return nil, nil
}
// Compare the parameters, which should be identical
if !state.Equal(result.State) {
p.ErrorLock.Lock()
p.Error = multierror.Append(p.Error, fmt.Errorf(
"Refresh %q had unequal states (real, then shadow):\n\n%#v\n\n%#v",
key, result.State, state))
p.ErrorLock.Unlock()
}
return result.Result, result.ResultErr
}
func (p *shadowResourceProviderShadow) ValidateDataSource(
t string, c *ResourceConfig) ([]string, []error) {
// Unique key
key := t
// Get the initial value
raw := p.Shared.ValidateDataSource.Value(key)
// Find a validation with our configuration
var result *shadowResourceProviderValidateDataSource
for {
// Get the value
if raw == nil {
p.ErrorLock.Lock()
defer p.ErrorLock.Unlock()
p.Error = multierror.Append(p.Error, fmt.Errorf(
"Unknown 'ValidateDataSource' call for %q:\n\n%#v",
key, c))
return nil, nil
}
wrapper, ok := raw.(*shadowResourceProviderValidateDataSourceWrapper)
if !ok {
p.ErrorLock.Lock()
defer p.ErrorLock.Unlock()
p.Error = multierror.Append(p.Error, fmt.Errorf(
"Unknown 'ValidateDataSource' shadow value: %#v", raw))
return nil, nil
}
// Look for the matching call with our configuration
wrapper.RLock()
for _, call := range wrapper.Calls {
if call.Config.Equal(c) {
result = call
break
}
}
wrapper.RUnlock()
// If we found a result, exit
if result != nil {
break
}
// Wait for a change so we can get the wrapper again
raw = p.Shared.ValidateDataSource.WaitForChange(key)
}
return result.Warns, result.Errors
}
func (p *shadowResourceProviderShadow) ReadDataDiff(
info *InstanceInfo,
desired *ResourceConfig) (*InstanceDiff, error) {
// Unique key
key := info.uniqueId()
raw := p.Shared.ReadDataDiff.Value(key)
if raw == nil {
p.ErrorLock.Lock()
defer p.ErrorLock.Unlock()
p.Error = multierror.Append(p.Error, fmt.Errorf(
"Unknown 'ReadDataDiff' call for %q:\n\n%#v",
key, desired))
return nil, nil
}
result, ok := raw.(*shadowResourceProviderReadDataDiff)
if !ok {
p.ErrorLock.Lock()
defer p.ErrorLock.Unlock()
p.Error = multierror.Append(p.Error, fmt.Errorf(
"Unknown 'ReadDataDiff' shadow value for %q: %#v", key, raw))
return nil, nil
}
// Compare the parameters, which should be identical
if !desired.Equal(result.Desired) {
p.ErrorLock.Lock()
p.Error = multierror.Append(p.Error, fmt.Errorf(
"ReadDataDiff %q had unequal configs (real, then shadow):\n\n%#v\n\n%#v",
key, result.Desired, desired))
p.ErrorLock.Unlock()
}
return result.Result, result.ResultErr
}
func (p *shadowResourceProviderShadow) ReadDataApply(
info *InstanceInfo,
d *InstanceDiff) (*InstanceState, error) {
// Unique key
key := info.uniqueId()
raw := p.Shared.ReadDataApply.Value(key)
if raw == nil {
p.ErrorLock.Lock()
defer p.ErrorLock.Unlock()
p.Error = multierror.Append(p.Error, fmt.Errorf(
"Unknown 'ReadDataApply' call for %q:\n\n%#v",
key, d))
return nil, nil
}
result, ok := raw.(*shadowResourceProviderReadDataApply)
if !ok {
p.ErrorLock.Lock()
defer p.ErrorLock.Unlock()
p.Error = multierror.Append(p.Error, fmt.Errorf(
"Unknown 'ReadDataApply' shadow value for %q: %#v", key, raw))
return nil, nil
}
// Compare the parameters, which should be identical
if !d.Equal(result.Diff) {
p.ErrorLock.Lock()
p.Error = multierror.Append(p.Error, fmt.Errorf(
"ReadDataApply: unequal diffs (real, then shadow):\n\n%#v\n\n%#v",
result.Diff, d))
p.ErrorLock.Unlock()
}
return result.Result, result.ResultErr
}
func (p *shadowResourceProviderShadow) ImportState(info *InstanceInfo, id string) ([]*InstanceState, error) {
panic("import not supported by shadow graph")
}
// The structs for the various function calls are put below. These structs
// are used to carry call information across the real/shadow boundaries.
type shadowResourceProviderInput struct {
Config *ResourceConfig
Result *ResourceConfig
ResultErr error
}
type shadowResourceProviderValidate struct {
Config *ResourceConfig
ResultWarn []string
ResultErr []error
}
type shadowResourceProviderConfigure struct {
Config *ResourceConfig
Result error
}
type shadowResourceProviderValidateResourceWrapper struct {
sync.RWMutex
Calls []*shadowResourceProviderValidateResource
}
type shadowResourceProviderValidateResource struct {
Config *ResourceConfig
Warns []string
Errors []error
}
type shadowResourceProviderApply struct {
State *InstanceState
Diff *InstanceDiff
Result *InstanceState
ResultErr error
}
type shadowResourceProviderDiff struct {
State *InstanceState
Desired *ResourceConfig
Result *InstanceDiff
ResultErr error
}
type shadowResourceProviderRefresh struct {
State *InstanceState
Result *InstanceState
ResultErr error
}
type shadowResourceProviderValidateDataSourceWrapper struct {
sync.RWMutex
Calls []*shadowResourceProviderValidateDataSource
}
type shadowResourceProviderValidateDataSource struct {
Config *ResourceConfig
Warns []string
Errors []error
}
type shadowResourceProviderReadDataDiff struct {
Desired *ResourceConfig
Result *InstanceDiff
ResultErr error
}
type shadowResourceProviderReadDataApply struct {
Diff *InstanceDiff
Result *InstanceState
ResultErr error
}