opentofu/terraform/shadow_resource_provisioner.go
Mitchell Hashimoto f8c7b639c9
terraform: switch to Context for stop, Stoppable provisioners
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.
2017-01-26 15:03:27 -08:00

283 lines
6.7 KiB
Go

package terraform
import (
"fmt"
"io"
"log"
"sync"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/terraform/helper/shadow"
)
// shadowResourceProvisioner implements ResourceProvisioner for the shadow
// eval context defined in eval_context_shadow.go.
//
// This is used to verify behavior with a real provisioner. This shouldn't
// be used directly.
type shadowResourceProvisioner interface {
ResourceProvisioner
Shadow
}
// newShadowResourceProvisioner creates a new shadowed ResourceProvisioner.
func newShadowResourceProvisioner(
p ResourceProvisioner) (ResourceProvisioner, shadowResourceProvisioner) {
// Create the shared data
shared := shadowResourceProvisionerShared{
Validate: shadow.ComparedValue{
Func: shadowResourceProvisionerValidateCompare,
},
}
// Create the real provisioner that does actual work
real := &shadowResourceProvisionerReal{
ResourceProvisioner: p,
Shared: &shared,
}
// Create the shadow that watches the real value
shadow := &shadowResourceProvisionerShadow{
Shared: &shared,
}
return real, shadow
}
// shadowResourceProvisionerReal is the real resource provisioner. Function calls
// to this will perform real work. This records the parameters and return
// values and call order for the shadow to reproduce.
type shadowResourceProvisionerReal struct {
ResourceProvisioner
Shared *shadowResourceProvisionerShared
}
func (p *shadowResourceProvisionerReal) Close() error {
var result error
if c, ok := p.ResourceProvisioner.(ResourceProvisionerCloser); ok {
result = c.Close()
}
p.Shared.CloseErr.SetValue(result)
return result
}
func (p *shadowResourceProvisionerReal) Validate(c *ResourceConfig) ([]string, []error) {
warns, errs := p.ResourceProvisioner.Validate(c)
p.Shared.Validate.SetValue(&shadowResourceProvisionerValidate{
Config: c,
ResultWarn: warns,
ResultErr: errs,
})
return warns, errs
}
func (p *shadowResourceProvisionerReal) Apply(
output UIOutput, s *InstanceState, c *ResourceConfig) error {
err := p.ResourceProvisioner.Apply(output, s, c)
// Write the result, grab a lock for writing. This should nver
// block long since the operations below don't block.
p.Shared.ApplyLock.Lock()
defer p.Shared.ApplyLock.Unlock()
key := s.ID
raw, ok := p.Shared.Apply.ValueOk(key)
if !ok {
// Setup a new value
raw = &shadow.ComparedValue{
Func: shadowResourceProvisionerApplyCompare,
}
// Set it
p.Shared.Apply.SetValue(key, raw)
}
compareVal, ok := raw.(*shadow.ComparedValue)
if !ok {
// Just log and return so that we don't cause the real side
// any side effects.
log.Printf("[ERROR] unknown value in 'apply': %#v", raw)
return err
}
// Write the resulting value
compareVal.SetValue(&shadowResourceProvisionerApply{
Config: c,
ResultErr: err,
})
return err
}
func (p *shadowResourceProvisionerReal) Stop() error {
return p.ResourceProvisioner.Stop()
}
// shadowResourceProvisionerShadow is the shadow resource provisioner. Function
// calls never affect real resources. This is paired with the "real" side
// which must be called properly to enable recording.
type shadowResourceProvisionerShadow struct {
Shared *shadowResourceProvisionerShared
Error error // Error is the list of errors from the shadow
ErrorLock sync.Mutex
}
type shadowResourceProvisionerShared 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
Validate shadow.ComparedValue
Apply shadow.KeyedValue
ApplyLock sync.Mutex // For writing only
}
func (p *shadowResourceProvisionerShared) Close() error {
closers := []io.Closer{
&p.CloseErr,
}
for _, c := range closers {
// This should never happen, but we don't panic because a panic
// could affect the real behavior of Terraform and a shadow should
// never be able to do that.
if err := c.Close(); err != nil {
return err
}
}
return nil
}
func (p *shadowResourceProvisionerShadow) CloseShadow() error {
err := p.Shared.Close()
if err != nil {
err = fmt.Errorf("close error: %s", err)
}
return err
}
func (p *shadowResourceProvisionerShadow) ShadowError() error {
return p.Error
}
func (p *shadowResourceProvisionerShadow) Close() error {
v := p.Shared.CloseErr.Value()
if v == nil {
return nil
}
return v.(error)
}
func (p *shadowResourceProvisionerShadow) Validate(c *ResourceConfig) ([]string, []error) {
// Get the result of the validate call
raw := p.Shared.Validate.Value(c)
if raw == nil {
return nil, nil
}
result, ok := raw.(*shadowResourceProvisionerValidate)
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
}
// We don't need to compare configurations because we key on the
// configuration so just return right away.
return result.ResultWarn, result.ResultErr
}
func (p *shadowResourceProvisionerShadow) Apply(
output UIOutput, s *InstanceState, c *ResourceConfig) error {
// Get the value based on the key
key := s.ID
raw := p.Shared.Apply.Value(key)
if raw == nil {
return nil
}
compareVal, ok := raw.(*shadow.ComparedValue)
if !ok {
p.ErrorLock.Lock()
defer p.ErrorLock.Unlock()
p.Error = multierror.Append(p.Error, fmt.Errorf(
"Unknown 'apply' shadow value: %#v", raw))
return nil
}
// With the compared value, we compare against our config
raw = compareVal.Value(c)
if raw == nil {
return nil
}
result, ok := raw.(*shadowResourceProvisionerApply)
if !ok {
p.ErrorLock.Lock()
defer p.ErrorLock.Unlock()
p.Error = multierror.Append(p.Error, fmt.Errorf(
"Unknown 'apply' shadow value: %#v", raw))
return nil
}
return result.ResultErr
}
func (p *shadowResourceProvisionerShadow) Stop() error {
// For the shadow, we always just return nil since a Stop indicates
// that we were interrupted and shadows are disabled during interrupts
// anyways.
return nil
}
// The structs for the various function calls are put below. These structs
// are used to carry call information across the real/shadow boundaries.
type shadowResourceProvisionerValidate struct {
Config *ResourceConfig
ResultWarn []string
ResultErr []error
}
type shadowResourceProvisionerApply struct {
Config *ResourceConfig
ResultErr error
}
func shadowResourceProvisionerValidateCompare(k, v interface{}) bool {
c, ok := k.(*ResourceConfig)
if !ok {
return false
}
result, ok := v.(*shadowResourceProvisionerValidate)
if !ok {
return false
}
return c.Equal(result.Config)
}
func shadowResourceProvisionerApplyCompare(k, v interface{}) bool {
c, ok := k.(*ResourceConfig)
if !ok {
return false
}
result, ok := v.(*shadowResourceProvisionerApply)
if !ok {
return false
}
return c.Equal(result.Config)
}