helper/shadow: Close for auto-closing all values
Fixes #10122 The simple fix was that we forgot to close `ReadDataApply` for the provider. But I've always felt that this section of the code was brittle and I wanted to put in a more robust solution. The `shadow.Close` method uses reflection to automatically close all values.
This commit is contained in:
parent
7fbd880390
commit
6557a3de18
|
@ -0,0 +1,80 @@
|
||||||
|
package shadow
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/hashicorp/go-multierror"
|
||||||
|
"github.com/mitchellh/reflectwalk"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Close will close all shadow values within the given structure.
|
||||||
|
//
|
||||||
|
// This uses reflection to walk the structure, find all shadow elements,
|
||||||
|
// and close them. Currently this will only find struct fields that are
|
||||||
|
// shadow values, and not slice elements, etc.
|
||||||
|
func Close(v interface{}) error {
|
||||||
|
// We require a pointer so we can address the internal fields
|
||||||
|
val := reflect.ValueOf(v)
|
||||||
|
if val.Kind() != reflect.Ptr {
|
||||||
|
return fmt.Errorf("value must be a pointer")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Walk and close
|
||||||
|
var w closeWalker
|
||||||
|
if err := reflectwalk.Walk(v, &w); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return w.Err
|
||||||
|
}
|
||||||
|
|
||||||
|
type closeWalker struct {
|
||||||
|
Err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *closeWalker) Struct(reflect.Value) error {
|
||||||
|
// Do nothing. We implement this for reflectwalk.StructWalker
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *closeWalker) StructField(f reflect.StructField, v reflect.Value) error {
|
||||||
|
// Not sure why this would be but lets avoid some panics
|
||||||
|
if !v.IsValid() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty for exported, so don't check unexported fields
|
||||||
|
if f.PkgPath != "" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify the io.Closer is in this package
|
||||||
|
typ := v.Type()
|
||||||
|
if typ.PkgPath() != "github.com/hashicorp/terraform/helper/shadow" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// We're looking for an io.Closer
|
||||||
|
raw := v.Interface()
|
||||||
|
if raw == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
closer, ok := raw.(io.Closer)
|
||||||
|
if !ok && v.CanAddr() {
|
||||||
|
closer, ok = v.Addr().Interface().(io.Closer)
|
||||||
|
}
|
||||||
|
if !ok {
|
||||||
|
return reflectwalk.SkipEntry
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close it
|
||||||
|
if err := closer.Close(); err != nil {
|
||||||
|
w.Err = multierror.Append(w.Err, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't go into the struct field
|
||||||
|
return reflectwalk.SkipEntry
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
package shadow
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestClose(t *testing.T) {
|
||||||
|
var foo struct {
|
||||||
|
A Value
|
||||||
|
B KeyedValue
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := Close(&foo); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := foo.A.Value(); v != ErrClosed {
|
||||||
|
t.Fatalf("bad: %#v", v)
|
||||||
|
}
|
||||||
|
if v := foo.B.Value("foo"); v != ErrClosed {
|
||||||
|
t.Fatalf("bad: %#v", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClose_nonPtr(t *testing.T) {
|
||||||
|
var foo struct{}
|
||||||
|
|
||||||
|
if err := Close(foo); err == nil {
|
||||||
|
t.Fatal("should error")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestClose_unexported(t *testing.T) {
|
||||||
|
var foo struct {
|
||||||
|
A Value
|
||||||
|
b Value
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := Close(&foo); err != nil {
|
||||||
|
t.Fatalf("err: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if v := foo.A.Value(); v != ErrClosed {
|
||||||
|
t.Fatalf("bad: %#v", v)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start trying to get the value
|
||||||
|
valueCh := make(chan interface{})
|
||||||
|
go func() {
|
||||||
|
valueCh <- foo.b.Value()
|
||||||
|
}()
|
||||||
|
|
||||||
|
// We should not get the value
|
||||||
|
select {
|
||||||
|
case <-valueCh:
|
||||||
|
t.Fatal("shouldn't receive value")
|
||||||
|
case <-time.After(10 * time.Millisecond):
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the value
|
||||||
|
foo.b.Close()
|
||||||
|
val := <-valueCh
|
||||||
|
|
||||||
|
// Verify
|
||||||
|
if val != ErrClosed {
|
||||||
|
t.Fatalf("bad: %#v", val)
|
||||||
|
}
|
||||||
|
}
|
|
@ -2,7 +2,6 @@ package terraform
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"log"
|
"log"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
@ -309,22 +308,7 @@ type shadowResourceProviderShared struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *shadowResourceProviderShared) Close() error {
|
func (p *shadowResourceProviderShared) Close() error {
|
||||||
closers := []io.Closer{
|
return shadow.Close(p)
|
||||||
&p.CloseErr, &p.Input, &p.Validate,
|
|
||||||
&p.Configure, &p.ValidateResource, &p.Apply, &p.Diff,
|
|
||||||
&p.Refresh, &p.ValidateDataSource, &p.ReadDataDiff,
|
|
||||||
}
|
|
||||||
|
|
||||||
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 *shadowResourceProviderShadow) CloseShadow() error {
|
func (p *shadowResourceProviderShadow) CloseShadow() error {
|
||||||
|
|
Loading…
Reference in New Issue