Merge pull request #2406 from svanharmelen/b-close-providers-provisioners
core: close provider/provisioner connections when not used anymore
This commit is contained in:
commit
01a9ceeed4
|
@ -3,9 +3,9 @@ package rpc
|
|||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/hashicorp/yamux"
|
||||
|
@ -17,7 +17,6 @@ import (
|
|||
// or accept a connection from, and the broker handles the details of
|
||||
// holding these channels open while they're being negotiated.
|
||||
type muxBroker struct {
|
||||
nextId uint32
|
||||
session *yamux.Session
|
||||
streams map[uint32]*muxBrokerPending
|
||||
|
||||
|
@ -95,9 +94,12 @@ func (m *muxBroker) Dial(id uint32) (net.Conn, error) {
|
|||
return stream, nil
|
||||
}
|
||||
|
||||
// NextId returns a unique ID to use next.
|
||||
// NextId returns a unique ID to use next. There is no need for seeding the
|
||||
// rand package as the returned ID's aren't stored or used anywhere outside
|
||||
// the current runtime. So it's perfectly fine to get the same pseudo-random
|
||||
// numbers each time terraform is running.
|
||||
func (m *muxBroker) NextId() uint32 {
|
||||
return atomic.AddUint32(&m.nextId, 1)
|
||||
return rand.Uint32()
|
||||
}
|
||||
|
||||
// Run starts the brokering and should be executed in a goroutine, since it
|
||||
|
|
|
@ -174,6 +174,10 @@ func (p *ResourceProvider) Resources() []terraform.ResourceType {
|
|||
return result
|
||||
}
|
||||
|
||||
func (p *ResourceProvider) Close() error {
|
||||
return p.Client.Close()
|
||||
}
|
||||
|
||||
// ResourceProviderServer is a net/rpc compatible structure for serving
|
||||
// a ResourceProvider. This should not be used directly.
|
||||
type ResourceProviderServer struct {
|
||||
|
|
|
@ -488,3 +488,31 @@ func TestResourceProvider_validateResource_warns(t *testing.T) {
|
|||
t.Fatalf("bad: %#v", w)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceProvider_close(t *testing.T) {
|
||||
client, _ := testNewClientServer(t)
|
||||
defer client.Close()
|
||||
|
||||
provider, err := client.ResourceProvider()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
var p interface{}
|
||||
p = provider
|
||||
pCloser, ok := p.(terraform.ResourceProviderCloser)
|
||||
if !ok {
|
||||
t.Fatal("should be a ResourceProviderCloser")
|
||||
}
|
||||
|
||||
if err := pCloser.Close(); err != nil {
|
||||
t.Fatalf("failed to close provider: %s", err)
|
||||
}
|
||||
|
||||
// The connection should be closed now, so if we to make a
|
||||
// new call we should get an error.
|
||||
err = provider.Configure(&terraform.ResourceConfig{})
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -63,6 +63,10 @@ func (p *ResourceProvisioner) Apply(
|
|||
return err
|
||||
}
|
||||
|
||||
func (p *ResourceProvisioner) Close() error {
|
||||
return p.Client.Close()
|
||||
}
|
||||
|
||||
type ResourceProvisionerValidateArgs struct {
|
||||
Config *terraform.ResourceConfig
|
||||
}
|
||||
|
|
|
@ -132,3 +132,34 @@ func TestResourceProvisioner_validate_warns(t *testing.T) {
|
|||
t.Fatalf("bad: %#v", w)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceProvisioner_close(t *testing.T) {
|
||||
client, _ := testNewClientServer(t)
|
||||
defer client.Close()
|
||||
|
||||
provisioner, err := client.ResourceProvisioner()
|
||||
if err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
var p interface{}
|
||||
p = provisioner
|
||||
pCloser, ok := p.(terraform.ResourceProvisionerCloser)
|
||||
if !ok {
|
||||
t.Fatal("should be a ResourceProvisionerCloser")
|
||||
}
|
||||
|
||||
if err := pCloser.Close(); err != nil {
|
||||
t.Fatalf("failed to close provisioner: %s", err)
|
||||
}
|
||||
|
||||
// The connection should be closed now, so if we to make a
|
||||
// new call we should get an error.
|
||||
o := &terraform.MockUIOutput{}
|
||||
s := &terraform.InstanceState{}
|
||||
c := &terraform.ResourceConfig{}
|
||||
err = provisioner.Apply(o, s, c)
|
||||
if err == nil {
|
||||
t.Fatal("should have error")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,9 @@ type EvalContext interface {
|
|||
// initialized) or returns nil if the provider isn't initialized.
|
||||
Provider(string) ResourceProvider
|
||||
|
||||
// CloseProvider closes provider connections that aren't needed anymore.
|
||||
CloseProvider(string) error
|
||||
|
||||
// ConfigureProvider configures the provider with the given
|
||||
// configuration. This is a separate context call because this call
|
||||
// is used to store the provider configuration for inheritance lookups
|
||||
|
@ -51,6 +54,10 @@ type EvalContext interface {
|
|||
// initialized) or returns nil if the provisioner isn't initialized.
|
||||
Provisioner(string) ResourceProvisioner
|
||||
|
||||
// CloseProvisioner closes provisioner connections that aren't needed
|
||||
// anymore.
|
||||
CloseProvisioner(string) error
|
||||
|
||||
// Interpolate takes the given raw configuration and completes
|
||||
// the interpolations, returning the processed ResourceConfig.
|
||||
//
|
||||
|
|
|
@ -114,6 +114,28 @@ func (ctx *BuiltinEvalContext) Provider(n string) ResourceProvider {
|
|||
return ctx.ProviderCache[PathCacheKey(providerPath)]
|
||||
}
|
||||
|
||||
func (ctx *BuiltinEvalContext) CloseProvider(n string) error {
|
||||
ctx.once.Do(ctx.init)
|
||||
|
||||
ctx.ProviderLock.Lock()
|
||||
defer ctx.ProviderLock.Unlock()
|
||||
|
||||
providerPath := make([]string, len(ctx.Path())+1)
|
||||
copy(providerPath, ctx.Path())
|
||||
providerPath[len(providerPath)-1] = n
|
||||
|
||||
var provider interface{}
|
||||
provider = ctx.ProviderCache[PathCacheKey(providerPath)]
|
||||
if provider != nil {
|
||||
if p, ok := provider.(ResourceProviderCloser); ok {
|
||||
delete(ctx.ProviderCache, PathCacheKey(providerPath))
|
||||
return p.Close()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ctx *BuiltinEvalContext) ConfigureProvider(
|
||||
n string, cfg *ResourceConfig) error {
|
||||
p := ctx.Provider(n)
|
||||
|
@ -222,6 +244,28 @@ func (ctx *BuiltinEvalContext) Provisioner(n string) ResourceProvisioner {
|
|||
return ctx.ProvisionerCache[PathCacheKey(provPath)]
|
||||
}
|
||||
|
||||
func (ctx *BuiltinEvalContext) CloseProvisioner(n string) error {
|
||||
ctx.once.Do(ctx.init)
|
||||
|
||||
ctx.ProvisionerLock.Lock()
|
||||
defer ctx.ProvisionerLock.Unlock()
|
||||
|
||||
provPath := make([]string, len(ctx.Path())+1)
|
||||
copy(provPath, ctx.Path())
|
||||
provPath[len(provPath)-1] = n
|
||||
|
||||
var prov interface{}
|
||||
prov = ctx.ProvisionerCache[PathCacheKey(provPath)]
|
||||
if prov != nil {
|
||||
if p, ok := prov.(ResourceProvisionerCloser); ok {
|
||||
delete(ctx.ProvisionerCache, PathCacheKey(provPath))
|
||||
return p.Close()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ctx *BuiltinEvalContext) Interpolate(
|
||||
cfg *config.RawConfig, r *Resource) (*ResourceConfig, error) {
|
||||
if cfg != nil {
|
||||
|
|
|
@ -25,6 +25,10 @@ type MockEvalContext struct {
|
|||
ProviderName string
|
||||
ProviderProvider ResourceProvider
|
||||
|
||||
CloseProviderCalled bool
|
||||
CloseProviderName string
|
||||
CloseProviderProvider ResourceProvider
|
||||
|
||||
ProviderInputCalled bool
|
||||
ProviderInputName string
|
||||
ProviderInputConfig map[string]interface{}
|
||||
|
@ -55,6 +59,10 @@ type MockEvalContext struct {
|
|||
ProvisionerName string
|
||||
ProvisionerProvisioner ResourceProvisioner
|
||||
|
||||
CloseProvisionerCalled bool
|
||||
CloseProvisionerName string
|
||||
CloseProvisionerProvisioner ResourceProvisioner
|
||||
|
||||
InterpolateCalled bool
|
||||
InterpolateConfig *config.RawConfig
|
||||
InterpolateResource *Resource
|
||||
|
@ -105,6 +113,12 @@ func (c *MockEvalContext) Provider(n string) ResourceProvider {
|
|||
return c.ProviderProvider
|
||||
}
|
||||
|
||||
func (c *MockEvalContext) CloseProvider(n string) error {
|
||||
c.CloseProviderCalled = true
|
||||
c.CloseProviderName = n
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *MockEvalContext) ConfigureProvider(n string, cfg *ResourceConfig) error {
|
||||
c.ConfigureProviderCalled = true
|
||||
c.ConfigureProviderName = n
|
||||
|
@ -150,6 +164,12 @@ func (c *MockEvalContext) Provisioner(n string) ResourceProvisioner {
|
|||
return c.ProvisionerProvisioner
|
||||
}
|
||||
|
||||
func (c *MockEvalContext) CloseProvisioner(n string) error {
|
||||
c.CloseProvisionerCalled = true
|
||||
c.CloseProvisionerName = n
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *MockEvalContext) Interpolate(
|
||||
config *config.RawConfig, resource *Resource) (*ResourceConfig, error) {
|
||||
c.InterpolateCalled = true
|
||||
|
|
|
@ -71,6 +71,17 @@ func (n *EvalInitProvider) Eval(ctx EvalContext) (interface{}, error) {
|
|||
return ctx.InitProvider(n.Name)
|
||||
}
|
||||
|
||||
// EvalCloseProvider is an EvalNode implementation that closes provider
|
||||
// connections that aren't needed anymore.
|
||||
type EvalCloseProvider struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
func (n *EvalCloseProvider) Eval(ctx EvalContext) (interface{}, error) {
|
||||
ctx.CloseProvider(n.Name)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// EvalGetProvider is an EvalNode implementation that retrieves an already
|
||||
// initialized provider instance for the given name.
|
||||
type EvalGetProvider struct {
|
||||
|
|
|
@ -112,6 +112,22 @@ func TestEvalInitProvider(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestEvalCloseProvider(t *testing.T) {
|
||||
n := &EvalCloseProvider{Name: "foo"}
|
||||
provider := &MockResourceProvider{}
|
||||
ctx := &MockEvalContext{CloseProviderProvider: provider}
|
||||
if _, err := n.Eval(ctx); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if !ctx.CloseProviderCalled {
|
||||
t.Fatal("should be called")
|
||||
}
|
||||
if ctx.CloseProviderName != "foo" {
|
||||
t.Fatalf("bad: %#v", ctx.CloseProviderName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvalGetProvider_impl(t *testing.T) {
|
||||
var _ EvalNode = new(EvalGetProvider)
|
||||
}
|
||||
|
|
|
@ -15,6 +15,17 @@ func (n *EvalInitProvisioner) Eval(ctx EvalContext) (interface{}, error) {
|
|||
return ctx.InitProvisioner(n.Name)
|
||||
}
|
||||
|
||||
// EvalCloseProvisioner is an EvalNode implementation that closes provisioner
|
||||
// connections that aren't needed anymore.
|
||||
type EvalCloseProvisioner struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
func (n *EvalCloseProvisioner) Eval(ctx EvalContext) (interface{}, error) {
|
||||
ctx.CloseProvisioner(n.Name)
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// EvalGetProvisioner is an EvalNode implementation that retrieves an already
|
||||
// initialized provisioner instance for the given name.
|
||||
type EvalGetProvisioner struct {
|
||||
|
|
|
@ -24,6 +24,22 @@ func TestEvalInitProvisioner(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestEvalCloseProvisioner(t *testing.T) {
|
||||
n := &EvalCloseProvisioner{Name: "foo"}
|
||||
provisioner := &MockResourceProvisioner{}
|
||||
ctx := &MockEvalContext{CloseProvisionerProvisioner: provisioner}
|
||||
if _, err := n.Eval(ctx); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
|
||||
if !ctx.CloseProvisionerCalled {
|
||||
t.Fatal("should be called")
|
||||
}
|
||||
if ctx.CloseProvisionerName != "foo" {
|
||||
t.Fatalf("bad: %#v", ctx.CloseProvisionerName)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEvalGetProvisioner_impl(t *testing.T) {
|
||||
var _ EvalNode = new(EvalGetProvisioner)
|
||||
}
|
||||
|
|
|
@ -72,3 +72,9 @@ func ProviderEvalTree(n string, config *config.RawConfig) EvalNode {
|
|||
|
||||
return &EvalSequence{Nodes: seq}
|
||||
}
|
||||
|
||||
// CloseProviderEvalTree returns the evaluation tree for closing
|
||||
// provider connections that aren't needed anymore.
|
||||
func CloseProviderEvalTree(n string) EvalNode {
|
||||
return &EvalCloseProvider{Name: n}
|
||||
}
|
||||
|
|
|
@ -116,12 +116,14 @@ func (b *BuiltinGraphBuilder) Steps(path []string) []GraphTransformer {
|
|||
// Provider-related transformations
|
||||
&MissingProviderTransformer{Providers: b.Providers},
|
||||
&ProviderTransformer{},
|
||||
&CloseProviderTransformer{},
|
||||
&PruneProviderTransformer{},
|
||||
&DisableProviderTransformer{},
|
||||
|
||||
// Provisioner-related transformations
|
||||
&MissingProvisionerTransformer{Provisioners: b.Provisioners},
|
||||
&ProvisionerTransformer{},
|
||||
&CloseProvisionerTransformer{},
|
||||
&PruneProvisionerTransformer{},
|
||||
|
||||
// Run our vertex-level transforms
|
||||
|
|
|
@ -196,6 +196,8 @@ aws_instance.db
|
|||
aws_instance.web
|
||||
aws_instance.db
|
||||
provider.aws
|
||||
provider.aws (close)
|
||||
aws_instance.web
|
||||
`
|
||||
|
||||
const testBuiltinGraphBuilderVerboseStr = `
|
||||
|
@ -213,8 +215,28 @@ aws_instance.web (destroy tainted)
|
|||
aws_instance.web (destroy)
|
||||
provider.aws
|
||||
provider.aws
|
||||
provider.aws (close)
|
||||
aws_instance.web
|
||||
`
|
||||
|
||||
const testBuiltinGraphBuilderMultiLevelStr = `
|
||||
module.foo.module.bar.output.value
|
||||
module.foo.module.bar.var.bar
|
||||
module.foo.module.bar.plan-destroy
|
||||
module.foo.module.bar.var.bar
|
||||
module.foo.var.foo
|
||||
module.foo.plan-destroy
|
||||
module.foo.var.foo
|
||||
root
|
||||
module.foo.module.bar.output.value
|
||||
module.foo.module.bar.plan-destroy
|
||||
module.foo.plan-destroy
|
||||
`
|
||||
|
||||
/*
|
||||
TODO: Commented out this const as it's likely this needs to
|
||||
be updated when the TestBuiltinGraphBuilder_modules test is
|
||||
enabled again.
|
||||
const testBuiltinGraphBuilderModuleStr = `
|
||||
aws_instance.web
|
||||
aws_instance.web (destroy)
|
||||
|
@ -231,17 +253,4 @@ module.consul (expanded)
|
|||
provider.aws
|
||||
provider.aws
|
||||
`
|
||||
|
||||
const testBuiltinGraphBuilderMultiLevelStr = `
|
||||
module.foo.module.bar.output.value
|
||||
module.foo.module.bar.var.bar
|
||||
module.foo.module.bar.plan-destroy
|
||||
module.foo.module.bar.var.bar
|
||||
module.foo.var.foo
|
||||
module.foo.plan-destroy
|
||||
module.foo.var.foo
|
||||
root
|
||||
module.foo.module.bar.output.value
|
||||
module.foo.module.bar.plan-destroy
|
||||
module.foo.plan-destroy
|
||||
`
|
||||
*/
|
||||
|
|
|
@ -71,6 +71,12 @@ type ResourceProvider interface {
|
|||
Refresh(*InstanceInfo, *InstanceState) (*InstanceState, error)
|
||||
}
|
||||
|
||||
// ResourceProviderCloser is an interface that providers that can close
|
||||
// connections that aren't needed anymore must implement.
|
||||
type ResourceProviderCloser interface {
|
||||
Close() error
|
||||
}
|
||||
|
||||
// ResourceType is a type of resource that a resource provider can manage.
|
||||
type ResourceType struct {
|
||||
Name string
|
||||
|
|
|
@ -23,6 +23,12 @@ type ResourceProvisioner interface {
|
|||
Apply(UIOutput, *InstanceState, *ResourceConfig) error
|
||||
}
|
||||
|
||||
// ResourceProvisionerCloser is an interface that provisioners that can close
|
||||
// connections that aren't needed anymore must implement.
|
||||
type ResourceProvisionerCloser interface {
|
||||
Close() error
|
||||
}
|
||||
|
||||
// ResourceProvisionerFactory is a function type that creates a new instance
|
||||
// of a resource provisioner.
|
||||
type ResourceProvisionerFactory func() (ResourceProvisioner, error)
|
||||
|
|
|
@ -17,6 +17,13 @@ type GraphNodeProvider interface {
|
|||
ProviderConfig() *config.RawConfig
|
||||
}
|
||||
|
||||
// GraphNodeCloseProvider is an interface that nodes that can be a close
|
||||
// provider must implement. The CloseProviderName returned is the name of
|
||||
// the provider they satisfy.
|
||||
type GraphNodeCloseProvider interface {
|
||||
CloseProviderName() string
|
||||
}
|
||||
|
||||
// GraphNodeProviderConsumer is an interface that nodes that require
|
||||
// a provider must implement. ProvidedBy must return the name of the provider
|
||||
// to use.
|
||||
|
@ -98,6 +105,37 @@ func (t *ProviderTransformer) Transform(g *Graph) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// CloseProviderTransformer is a GraphTransformer that adds nodes to the
|
||||
// graph that will close open provider connections that aren't needed anymore.
|
||||
// A provider connection is not needed anymore once all depended resources
|
||||
// in the graph are evaluated.
|
||||
type CloseProviderTransformer struct{}
|
||||
|
||||
func (t *CloseProviderTransformer) Transform(g *Graph) error {
|
||||
m := closeProviderVertexMap(g)
|
||||
for _, v := range g.Vertices() {
|
||||
if pv, ok := v.(GraphNodeProviderConsumer); ok {
|
||||
for _, p := range pv.ProvidedBy() {
|
||||
source := m[p]
|
||||
|
||||
if source == nil {
|
||||
// Create a new graphNodeCloseProvider and add it to the graph
|
||||
source = &graphNodeCloseProvider{ProviderNameValue: p}
|
||||
g.Add(source)
|
||||
|
||||
// Make sure we also add the new graphNodeCloseProvider to the map
|
||||
// so we don't create and add any duplicate graphNodeCloseProviders.
|
||||
m[p] = source
|
||||
}
|
||||
|
||||
g.Connect(dag.BasicEdge(source, v))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MissingProviderTransformer is a GraphTransformer that adds nodes
|
||||
// for missing providers into the graph. Specifically, it creates provider
|
||||
// configuration nodes for all the providers that we support. These are
|
||||
|
@ -143,6 +181,28 @@ func (t *PruneProviderTransformer) Transform(g *Graph) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func providerVertexMap(g *Graph) map[string]dag.Vertex {
|
||||
m := make(map[string]dag.Vertex)
|
||||
for _, v := range g.Vertices() {
|
||||
if pv, ok := v.(GraphNodeProvider); ok {
|
||||
m[pv.ProviderName()] = v
|
||||
}
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func closeProviderVertexMap(g *Graph) map[string]dag.Vertex {
|
||||
m := make(map[string]dag.Vertex)
|
||||
for _, v := range g.Vertices() {
|
||||
if pv, ok := v.(GraphNodeCloseProvider); ok {
|
||||
m[pv.CloseProviderName()] = v
|
||||
}
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
type graphNodeDisabledProvider struct {
|
||||
GraphNodeProvider
|
||||
}
|
||||
|
@ -258,6 +318,89 @@ func (n *graphNodeDisabledProviderFlat) DependentOn() []string {
|
|||
return result
|
||||
}
|
||||
|
||||
type graphNodeCloseProvider struct {
|
||||
ProviderNameValue string
|
||||
}
|
||||
|
||||
func (n *graphNodeCloseProvider) Name() string {
|
||||
return fmt.Sprintf("provider.%s (close)", n.ProviderNameValue)
|
||||
}
|
||||
|
||||
// GraphNodeEvalable impl.
|
||||
func (n *graphNodeCloseProvider) EvalTree() EvalNode {
|
||||
return CloseProviderEvalTree(n.ProviderNameValue)
|
||||
}
|
||||
|
||||
// GraphNodeDependable impl.
|
||||
func (n *graphNodeCloseProvider) DependableName() []string {
|
||||
return []string{n.Name()}
|
||||
}
|
||||
|
||||
func (n *graphNodeCloseProvider) CloseProviderName() string {
|
||||
return n.ProviderNameValue
|
||||
}
|
||||
|
||||
// GraphNodeDotter impl.
|
||||
func (n *graphNodeCloseProvider) DotNode(name string, opts *GraphDotOpts) *dot.Node {
|
||||
return dot.NewNode(name, map[string]string{
|
||||
"label": n.Name(),
|
||||
"shape": "diamond",
|
||||
})
|
||||
}
|
||||
|
||||
// GraphNodeFlattenable impl.
|
||||
func (n *graphNodeCloseProvider) Flatten(p []string) (dag.Vertex, error) {
|
||||
return &graphNodeCloseProviderFlat{
|
||||
graphNodeCloseProvider: n,
|
||||
PathValue: p,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Same as graphNodeCloseProvider, but for flattening
|
||||
type graphNodeCloseProviderFlat struct {
|
||||
*graphNodeCloseProvider
|
||||
|
||||
PathValue []string
|
||||
}
|
||||
|
||||
func (n *graphNodeCloseProviderFlat) Name() string {
|
||||
return fmt.Sprintf(
|
||||
"%s.%s", modulePrefixStr(n.PathValue), n.graphNodeCloseProvider.Name())
|
||||
}
|
||||
|
||||
func (n *graphNodeCloseProviderFlat) Path() []string {
|
||||
return n.PathValue
|
||||
}
|
||||
|
||||
func (n *graphNodeCloseProviderFlat) ProviderName() string {
|
||||
return fmt.Sprintf(
|
||||
"%s.%s", modulePrefixStr(n.PathValue),
|
||||
n.graphNodeCloseProvider.CloseProviderName())
|
||||
}
|
||||
|
||||
// GraphNodeDependable impl.
|
||||
func (n *graphNodeCloseProviderFlat) DependableName() []string {
|
||||
return []string{n.Name()}
|
||||
}
|
||||
|
||||
func (n *graphNodeCloseProviderFlat) DependentOn() []string {
|
||||
var result []string
|
||||
|
||||
// If we're in a module, then depend on our parent's provider
|
||||
if len(n.PathValue) > 1 {
|
||||
prefix := modulePrefixStr(n.PathValue[:len(n.PathValue)-1])
|
||||
if prefix != "" {
|
||||
prefix += "."
|
||||
}
|
||||
|
||||
result = append(result, fmt.Sprintf(
|
||||
"%s%s",
|
||||
prefix, n.graphNodeCloseProvider.Name()))
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
type graphNodeMissingProvider struct {
|
||||
ProviderNameValue string
|
||||
}
|
||||
|
@ -305,17 +448,6 @@ func (n *graphNodeMissingProvider) Flatten(p []string) (dag.Vertex, error) {
|
|||
}, nil
|
||||
}
|
||||
|
||||
func providerVertexMap(g *Graph) map[string]dag.Vertex {
|
||||
m := make(map[string]dag.Vertex)
|
||||
for _, v := range g.Vertices() {
|
||||
if pv, ok := v.(GraphNodeProvider); ok {
|
||||
m[pv.ProviderName()] = v
|
||||
}
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// Same as graphNodeMissingProvider, but for flattening
|
||||
type graphNodeMissingProviderFlat struct {
|
||||
*graphNodeMissingProvider
|
||||
|
|
|
@ -30,6 +30,38 @@ func TestProviderTransformer(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestCloseProviderTransformer(t *testing.T) {
|
||||
mod := testModule(t, "transform-provider-basic")
|
||||
|
||||
g := Graph{Path: RootModulePath}
|
||||
{
|
||||
tf := &ConfigTransformer{Module: mod}
|
||||
if err := tf.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
transform := &ProviderTransformer{}
|
||||
if err := transform.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
transform := &CloseProviderTransformer{}
|
||||
if err := transform.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(g.String())
|
||||
expected := strings.TrimSpace(testTransformCloseProviderBasicStr)
|
||||
if actual != expected {
|
||||
t.Fatalf("bad:\n\n%s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMissingProviderTransformer(t *testing.T) {
|
||||
mod := testModule(t, "transform-provider-basic")
|
||||
|
||||
|
@ -41,9 +73,18 @@ func TestMissingProviderTransformer(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
transform := &MissingProviderTransformer{Providers: []string{"foo"}}
|
||||
if err := transform.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
{
|
||||
transform := &MissingProviderTransformer{Providers: []string{"foo"}}
|
||||
if err := transform.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
transform := &CloseProviderTransformer{}
|
||||
if err := transform.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(g.String())
|
||||
|
@ -78,6 +119,13 @@ func TestPruneProviderTransformer(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
{
|
||||
transform := &CloseProviderTransformer{}
|
||||
if err := transform.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
transform := &PruneProviderTransformer{}
|
||||
if err := transform.Transform(&g); err != nil {
|
||||
|
@ -117,6 +165,13 @@ func TestDisableProviderTransformer(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
{
|
||||
transform := &CloseProviderTransformer{}
|
||||
if err := transform.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
transform := &PruneProviderTransformer{}
|
||||
if err := transform.Transform(&g); err != nil {
|
||||
|
@ -163,6 +218,13 @@ func TestDisableProviderTransformer_keep(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
{
|
||||
transform := &CloseProviderTransformer{}
|
||||
if err := transform.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
transform := &PruneProviderTransformer{}
|
||||
if err := transform.Transform(&g); err != nil {
|
||||
|
@ -203,9 +265,19 @@ aws_instance.web
|
|||
provider.aws
|
||||
`
|
||||
|
||||
const testTransformCloseProviderBasicStr = `
|
||||
aws_instance.web
|
||||
provider.aws
|
||||
provider.aws
|
||||
provider.aws (close)
|
||||
aws_instance.web
|
||||
`
|
||||
|
||||
const testTransformMissingProviderBasicStr = `
|
||||
aws_instance.web
|
||||
provider.aws
|
||||
provider.aws (close)
|
||||
aws_instance.web
|
||||
provider.foo
|
||||
`
|
||||
|
||||
|
@ -213,12 +285,16 @@ const testTransformPruneProviderBasicStr = `
|
|||
foo_instance.web
|
||||
provider.foo
|
||||
provider.foo
|
||||
provider.foo (close)
|
||||
foo_instance.web
|
||||
`
|
||||
|
||||
const testTransformDisableProviderBasicStr = `
|
||||
module.child
|
||||
provider.aws (disabled)
|
||||
var.foo
|
||||
provider.aws (close)
|
||||
module.child
|
||||
provider.aws (disabled)
|
||||
var.foo
|
||||
`
|
||||
|
@ -230,5 +306,8 @@ module.child
|
|||
provider.aws
|
||||
var.foo
|
||||
provider.aws
|
||||
provider.aws (close)
|
||||
aws_instance.foo
|
||||
module.child
|
||||
var.foo
|
||||
`
|
||||
|
|
|
@ -14,6 +14,13 @@ type GraphNodeProvisioner interface {
|
|||
ProvisionerName() string
|
||||
}
|
||||
|
||||
// GraphNodeCloseProvisioner is an interface that nodes that can be a close
|
||||
// provisioner must implement. The CloseProvisionerName returned is the name
|
||||
// of the provisioner they satisfy.
|
||||
type GraphNodeCloseProvisioner interface {
|
||||
CloseProvisionerName() string
|
||||
}
|
||||
|
||||
// GraphNodeProvisionerConsumer is an interface that nodes that require
|
||||
// a provisioner must implement. ProvisionedBy must return the name of the
|
||||
// provisioner to use.
|
||||
|
@ -49,6 +56,37 @@ func (t *ProvisionerTransformer) Transform(g *Graph) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// CloseProvisionerTransformer is a GraphTransformer that adds nodes to the
|
||||
// graph that will close open provisioner connections that aren't needed
|
||||
// anymore. A provisioner connection is not needed anymore once all depended
|
||||
// resources in the graph are evaluated.
|
||||
type CloseProvisionerTransformer struct{}
|
||||
|
||||
func (t *CloseProvisionerTransformer) Transform(g *Graph) error {
|
||||
m := closeProvisionerVertexMap(g)
|
||||
for _, v := range g.Vertices() {
|
||||
if pv, ok := v.(GraphNodeProvisionerConsumer); ok {
|
||||
for _, p := range pv.ProvisionedBy() {
|
||||
source := m[p]
|
||||
|
||||
if source == nil {
|
||||
// Create a new graphNodeCloseProvisioner and add it to the graph
|
||||
source = &graphNodeCloseProvisioner{ProvisionerNameValue: p}
|
||||
g.Add(source)
|
||||
|
||||
// Make sure we also add the new graphNodeCloseProvisioner to the map
|
||||
// so we don't create and add any duplicate graphNodeCloseProvisioners.
|
||||
m[p] = source
|
||||
}
|
||||
|
||||
g.Connect(dag.BasicEdge(source, v))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MissingProvisionerTransformer is a GraphTransformer that adds nodes
|
||||
// for missing provisioners into the graph. Specifically, it creates provisioner
|
||||
// configuration nodes for all the provisioners that we support. These are
|
||||
|
@ -94,6 +132,75 @@ func (t *PruneProvisionerTransformer) Transform(g *Graph) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func provisionerVertexMap(g *Graph) map[string]dag.Vertex {
|
||||
m := make(map[string]dag.Vertex)
|
||||
for _, v := range g.Vertices() {
|
||||
if pv, ok := v.(GraphNodeProvisioner); ok {
|
||||
m[pv.ProvisionerName()] = v
|
||||
}
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func closeProvisionerVertexMap(g *Graph) map[string]dag.Vertex {
|
||||
m := make(map[string]dag.Vertex)
|
||||
for _, v := range g.Vertices() {
|
||||
if pv, ok := v.(GraphNodeCloseProvisioner); ok {
|
||||
m[pv.CloseProvisionerName()] = v
|
||||
}
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
type graphNodeCloseProvisioner struct {
|
||||
ProvisionerNameValue string
|
||||
}
|
||||
|
||||
func (n *graphNodeCloseProvisioner) Name() string {
|
||||
return fmt.Sprintf("provisioner.%s (close)", n.ProvisionerNameValue)
|
||||
}
|
||||
|
||||
// GraphNodeEvalable impl.
|
||||
func (n *graphNodeCloseProvisioner) EvalTree() EvalNode {
|
||||
return &EvalCloseProvisioner{Name: n.ProvisionerNameValue}
|
||||
}
|
||||
|
||||
func (n *graphNodeCloseProvisioner) CloseProvisionerName() string {
|
||||
return n.ProvisionerNameValue
|
||||
}
|
||||
|
||||
// GraphNodeFlattenable impl.
|
||||
func (n *graphNodeCloseProvisioner) Flatten(p []string) (dag.Vertex, error) {
|
||||
return &graphNodeCloseProvisionerFlat{
|
||||
graphNodeCloseProvisioner: n,
|
||||
PathValue: p,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Same as graphNodeCloseProvisioner, but for flattening
|
||||
type graphNodeCloseProvisionerFlat struct {
|
||||
*graphNodeCloseProvisioner
|
||||
|
||||
PathValue []string
|
||||
}
|
||||
|
||||
func (n *graphNodeCloseProvisionerFlat) Name() string {
|
||||
return fmt.Sprintf(
|
||||
"%s.%s", modulePrefixStr(n.PathValue), n.graphNodeCloseProvisioner.Name())
|
||||
}
|
||||
|
||||
func (n *graphNodeCloseProvisionerFlat) Path() []string {
|
||||
return n.PathValue
|
||||
}
|
||||
|
||||
func (n *graphNodeCloseProvisionerFlat) ProvisionerName() string {
|
||||
return fmt.Sprintf(
|
||||
"%s.%s", modulePrefixStr(n.PathValue),
|
||||
n.graphNodeCloseProvisioner.CloseProvisionerName())
|
||||
}
|
||||
|
||||
type graphNodeMissingProvisioner struct {
|
||||
ProvisionerNameValue string
|
||||
}
|
||||
|
@ -119,17 +226,6 @@ func (n *graphNodeMissingProvisioner) Flatten(p []string) (dag.Vertex, error) {
|
|||
}, nil
|
||||
}
|
||||
|
||||
func provisionerVertexMap(g *Graph) map[string]dag.Vertex {
|
||||
m := make(map[string]dag.Vertex)
|
||||
for _, v := range g.Vertices() {
|
||||
if pv, ok := v.(GraphNodeProvisioner); ok {
|
||||
m[pv.ProvisionerName()] = v
|
||||
}
|
||||
}
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
// Same as graphNodeMissingProvisioner, but for flattening
|
||||
type graphNodeMissingProvisionerFlat struct {
|
||||
*graphNodeMissingProvisioner
|
||||
|
|
|
@ -18,9 +18,18 @@ func TestMissingProvisionerTransformer(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
transform := &MissingProvisionerTransformer{Provisioners: []string{"foo"}}
|
||||
if err := transform.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
{
|
||||
transform := &MissingProvisionerTransformer{Provisioners: []string{"foo"}}
|
||||
if err := transform.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
transform := &CloseProvisionerTransformer{}
|
||||
if err := transform.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
actual := strings.TrimSpace(g.String())
|
||||
|
@ -56,6 +65,13 @@ func TestPruneProvisionerTransformer(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
{
|
||||
transform := &CloseProvisionerTransformer{}
|
||||
if err := transform.Transform(&g); err != nil {
|
||||
t.Fatalf("err: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
transform := &PruneProvisionerTransformer{}
|
||||
if err := transform.Transform(&g); err != nil {
|
||||
|
@ -86,10 +102,14 @@ func TestGraphNodeMissingProvisioner_ProvisionerName(t *testing.T) {
|
|||
const testTransformMissingProvisionerBasicStr = `
|
||||
aws_instance.web
|
||||
provisioner.foo
|
||||
provisioner.shell (close)
|
||||
aws_instance.web
|
||||
`
|
||||
|
||||
const testTransformPruneProvisionerBasicStr = `
|
||||
aws_instance.web
|
||||
provisioner.foo
|
||||
provisioner.foo
|
||||
provisioner.foo (close)
|
||||
aws_instance.web
|
||||
`
|
||||
|
|
Loading…
Reference in New Issue