terraform: switch to a component factory

This is necessary so that the shadow version can actually keep track of
what provider is used for what. Before, providers for different alises
were just initialized but the factory had no idea. Arguably this is fine
but when trying to build a shadow graph this presents challenges.

With these changes, we now pass an opaque "uid" through that is used to
keep track of the providers and what real maps to what shadow.
This commit is contained in:
Mitchell Hashimoto 2016-10-03 18:23:37 -07:00
parent 5053872e82
commit 0b00bbde4e
No known key found for this signature in database
GPG Key ID: 744E147AA52F5B0A
8 changed files with 280 additions and 180 deletions

View File

@ -72,13 +72,12 @@ type Context struct {
// that newShadowContext still does the right thing. Tests should // that newShadowContext still does the right thing. Tests should
// fail regardless but putting this note here as well. // fail regardless but putting this note here as well.
components contextComponentFactory
destroy bool destroy bool
diff *Diff diff *Diff
diffLock sync.RWMutex diffLock sync.RWMutex
hooks []Hook hooks []Hook
module *module.Tree module *module.Tree
providers map[string]ResourceProviderFactory
provisioners map[string]ResourceProvisionerFactory
sh *stopHook sh *stopHook
state *State state *State
stateLock sync.RWMutex stateLock sync.RWMutex
@ -153,12 +152,14 @@ func NewContext(opts *ContextOpts) (*Context, error) {
} }
return &Context{ return &Context{
components: &basicComponentFactory{
providers: opts.Providers,
provisioners: opts.Provisioners,
},
destroy: opts.Destroy, destroy: opts.Destroy,
diff: opts.Diff, diff: opts.Diff,
hooks: hooks, hooks: hooks,
module: opts.Module, module: opts.Module,
providers: opts.Providers,
provisioners: opts.Provisioners,
state: state, state: state,
targets: opts.Targets, targets: opts.Targets,
uiInput: opts.UIInput, uiInput: opts.UIInput,
@ -183,22 +184,11 @@ func (c *Context) Graph(g *ContextGraphOpts) (*Graph, error) {
// GraphBuilder returns the GraphBuilder that will be used to create // GraphBuilder returns the GraphBuilder that will be used to create
// the graphs for this context. // the graphs for this context.
func (c *Context) graphBuilder(g *ContextGraphOpts) GraphBuilder { func (c *Context) graphBuilder(g *ContextGraphOpts) GraphBuilder {
// TODO test
providers := make([]string, 0, len(c.providers))
for k, _ := range c.providers {
providers = append(providers, k)
}
provisioners := make([]string, 0, len(c.provisioners))
for k, _ := range c.provisioners {
provisioners = append(provisioners, k)
}
return &BuiltinGraphBuilder{ return &BuiltinGraphBuilder{
Root: c.module, Root: c.module,
Diff: c.diff, Diff: c.diff,
Providers: providers, Providers: c.components.ResourceProviders(),
Provisioners: provisioners, Provisioners: c.components.ResourceProvisioners(),
State: c.state, State: c.state,
Targets: c.targets, Targets: c.targets,
Destroy: c.destroy, Destroy: c.destroy,
@ -375,7 +365,7 @@ func (c *Context) Apply() (*State, error) {
if c.destroy { if c.destroy {
walker, err = c.walk(graph, nil, walkDestroy) walker, err = c.walk(graph, nil, walkDestroy)
} else { } else {
walker, err = c.walk(graph, graph, walkApply) walker, err = c.walk(graph, nil, walkApply)
} }
if len(walker.ValidationErrors) > 0 { if len(walker.ValidationErrors) > 0 {

View File

@ -0,0 +1,65 @@
package terraform
import (
"fmt"
)
// contextComponentFactory is the interface that Context uses
// to initialize various components such as providers and provisioners.
// This factory gets more information than the raw maps using to initialize
// a Context. This information is used for debugging.
type contextComponentFactory interface {
// ResourceProvider creates a new ResourceProvider with the given
// type. The "uid" is a unique identifier for this provider being
// initialized that can be used for internal tracking.
ResourceProvider(typ, uid string) (ResourceProvider, error)
ResourceProviders() []string
// ResourceProvisioner creates a new ResourceProvisioner with the
// given type. The "uid" is a unique identifier for this provisioner
// being initialized that can be used for internal tracking.
ResourceProvisioner(typ, uid string) (ResourceProvisioner, error)
ResourceProvisioners() []string
}
// basicComponentFactory just calls a factory from a map directly.
type basicComponentFactory struct {
providers map[string]ResourceProviderFactory
provisioners map[string]ResourceProvisionerFactory
}
func (c *basicComponentFactory) ResourceProviders() []string {
result := make([]string, len(c.providers))
for k, _ := range c.providers {
result = append(result, k)
}
return result
}
func (c *basicComponentFactory) ResourceProvisioners() []string {
result := make([]string, len(c.provisioners))
for k, _ := range c.provisioners {
result = append(result, k)
}
return result
}
func (c *basicComponentFactory) ResourceProvider(typ, uid string) (ResourceProvider, error) {
f, ok := c.providers[typ]
if !ok {
return nil, fmt.Errorf("unknown provider %q", typ)
}
return f()
}
func (c *basicComponentFactory) ResourceProvisioner(typ, uid string) (ResourceProvisioner, error) {
f, ok := c.provisioners[typ]
if !ok {
return nil, fmt.Errorf("unknown provisioner %q", typ)
}
return f()
}

View File

@ -43,17 +43,11 @@ func (c *Context) Import(opts *ImportOpts) (*State, error) {
// Copy our own state // Copy our own state
c.state = c.state.DeepCopy() c.state = c.state.DeepCopy()
// Get supported providers (for the graph builder)
providers := make([]string, 0, len(c.providers))
for k, _ := range c.providers {
providers = append(providers, k)
}
// Initialize our graph builder // Initialize our graph builder
builder := &ImportGraphBuilder{ builder := &ImportGraphBuilder{
ImportTargets: opts.Targets, ImportTargets: opts.Targets,
Module: opts.Module, Module: opts.Module,
Providers: providers, Providers: c.components.ResourceProviders(),
} }
// Build the graph! // Build the graph!

View File

@ -26,14 +26,13 @@ type BuiltinEvalContext struct {
InterpolaterVars map[string]map[string]interface{} InterpolaterVars map[string]map[string]interface{}
InterpolaterVarLock *sync.Mutex InterpolaterVarLock *sync.Mutex
Components contextComponentFactory
Hooks []Hook Hooks []Hook
InputValue UIInput InputValue UIInput
Providers map[string]ResourceProviderFactory
ProviderCache map[string]ResourceProvider ProviderCache map[string]ResourceProvider
ProviderConfigCache map[string]*ResourceConfig ProviderConfigCache map[string]*ResourceConfig
ProviderInputConfig map[string]map[string]interface{} ProviderInputConfig map[string]map[string]interface{}
ProviderLock *sync.Mutex ProviderLock *sync.Mutex
Provisioners map[string]ResourceProvisionerFactory
ProvisionerCache map[string]ResourceProvisioner ProvisionerCache map[string]ResourceProvisioner
ProvisionerLock *sync.Mutex ProvisionerLock *sync.Mutex
DiffValue *Diff DiffValue *Diff
@ -82,13 +81,9 @@ func (ctx *BuiltinEvalContext) InitProvider(n string) (ResourceProvider, error)
defer ctx.ProviderLock.Unlock() defer ctx.ProviderLock.Unlock()
typeName := strings.SplitN(n, ".", 2)[0] typeName := strings.SplitN(n, ".", 2)[0]
uid := n
f, ok := ctx.Providers[typeName] p, err := ctx.Components.ResourceProvider(typeName, uid)
if !ok {
return nil, fmt.Errorf("Provider '%s' not found", typeName)
}
p, err := f()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -231,12 +226,7 @@ func (ctx *BuiltinEvalContext) InitProvisioner(
ctx.ProvisionerLock.Lock() ctx.ProvisionerLock.Lock()
defer ctx.ProvisionerLock.Unlock() defer ctx.ProvisionerLock.Unlock()
f, ok := ctx.Provisioners[n] p, err := ctx.Components.ResourceProvisioner(n, n)
if !ok {
return nil, fmt.Errorf("Provisioner '%s' not found", n)
}
p, err := f()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -341,9 +331,4 @@ func (ctx *BuiltinEvalContext) State() (*State, *sync.RWMutex) {
} }
func (ctx *BuiltinEvalContext) init() { func (ctx *BuiltinEvalContext) init() {
// We nil-check the things below because they're meant to be configured,
// and we just default them to non-nil.
if ctx.Providers == nil {
ctx.Providers = make(map[string]ResourceProviderFactory)
}
} }

View File

@ -68,12 +68,11 @@ func (w *ContextGraphWalker) EnterPath(path []string) EvalContext {
PathValue: path, PathValue: path,
Hooks: w.Context.hooks, Hooks: w.Context.hooks,
InputValue: w.Context.uiInput, InputValue: w.Context.uiInput,
Providers: w.Context.providers, Components: w.Context.components,
ProviderCache: w.providerCache, ProviderCache: w.providerCache,
ProviderConfigCache: w.providerConfigCache, ProviderConfigCache: w.providerConfigCache,
ProviderInputConfig: w.Context.providerInputConfig, ProviderInputConfig: w.Context.providerInputConfig,
ProviderLock: &w.providerLock, ProviderLock: &w.providerLock,
Provisioners: w.Context.provisioners,
ProvisionerCache: w.provisionerCache, ProvisionerCache: w.provisionerCache,
ProvisionerLock: &w.provisionerLock, ProvisionerLock: &w.provisionerLock,
DiffValue: w.Context.diff, DiffValue: w.Context.diff,

View File

@ -0,0 +1,169 @@
package terraform
import (
"fmt"
"sync"
"github.com/hashicorp/terraform/helper/shadow"
)
// newShadowComponentFactory creates a shadowed contextComponentFactory
// so that requests to create new components result in both a real and
// shadow side.
func newShadowComponentFactory(
f contextComponentFactory) (contextComponentFactory, *shadowComponentFactory) {
// Create the shared data
shared := &shadowComponentFactoryShared{contextComponentFactory: f}
// Create the real side
real := &shadowComponentFactoryReal{
shadowComponentFactoryShared: shared,
}
// Create the shadow
shadow := &shadowComponentFactory{
shadowComponentFactoryShared: shared,
}
return real, shadow
}
// shadowComponentFactory is the shadow side. Any components created
// with this factory are fake and will not cause real work to happen.
type shadowComponentFactory struct {
*shadowComponentFactoryShared
}
func (f *shadowComponentFactory) ResourceProvider(
n, uid string) (ResourceProvider, error) {
_, shadow, err := f.shadowComponentFactoryShared.ResourceProvider(n, uid)
return shadow, err
}
func (f *shadowComponentFactory) ResourceProvisioner(
n, uid string) (ResourceProvisioner, error) {
_, shadow, err := f.shadowComponentFactoryShared.ResourceProvisioner(n, uid)
return shadow, err
}
// shadowComponentFactoryReal is the real side of the component factory.
// Operations here result in real components that do real work.
type shadowComponentFactoryReal struct {
*shadowComponentFactoryShared
}
func (f *shadowComponentFactoryReal) ResourceProvider(
n, uid string) (ResourceProvider, error) {
real, _, err := f.shadowComponentFactoryShared.ResourceProvider(n, uid)
return real, err
}
func (f *shadowComponentFactoryReal) ResourceProvisioner(
n, uid string) (ResourceProvisioner, error) {
real, _, err := f.shadowComponentFactoryShared.ResourceProvisioner(n, uid)
return real, err
}
// shadowComponentFactoryShared is shared data between the two factories.
type shadowComponentFactoryShared struct {
contextComponentFactory
providers shadow.KeyedValue
provisioners shadow.KeyedValue
lock sync.Mutex
}
// shadowResourceProviderFactoryEntry is the entry that is stored in
// the Shadows key/value for a provider.
type shadowComponentFactoryProviderEntry struct {
Real ResourceProvider
Shadow shadowResourceProvider
Err error
}
type shadowComponentFactoryProvisionerEntry struct {
Real ResourceProvisioner
Shadow ResourceProvisioner
Err error
}
func (f *shadowComponentFactoryShared) ResourceProvider(
n, uid string) (ResourceProvider, shadowResourceProvider, error) {
f.lock.Lock()
defer f.lock.Unlock()
// Determine if we already have a value
raw, ok := f.providers.ValueOk(uid)
if !ok {
// Build the entry
var entry shadowComponentFactoryProviderEntry
// No value, initialize. Create the original
p, err := f.contextComponentFactory.ResourceProvider(n, uid)
if err != nil {
entry.Err = err
p = nil // Just to be sure
}
if p != nil {
// Create the shadow
real, shadow := newShadowResourceProvider(p)
entry.Real = real
entry.Shadow = shadow
}
// Store the value
f.providers.SetValue(uid, &entry)
raw = &entry
}
// Read the entry
entry, ok := raw.(*shadowComponentFactoryProviderEntry)
if !ok {
return nil, nil, fmt.Errorf("Unknown value for shadow provider: %#v", raw)
}
// Return
return entry.Real, entry.Shadow, entry.Err
}
func (f *shadowComponentFactoryShared) ResourceProvisioner(
n, uid string) (ResourceProvisioner, ResourceProvisioner, error) {
f.lock.Lock()
defer f.lock.Unlock()
// Determine if we already have a value
raw, ok := f.provisioners.ValueOk(uid)
if !ok {
// Build the entry
var entry shadowComponentFactoryProvisionerEntry
// No value, initialize. Create the original
p, err := f.contextComponentFactory.ResourceProvisioner(n, uid)
if err != nil {
entry.Err = err
p = nil // Just to be sure
}
if p != nil {
// For now, just create a mock since we don't support provisioners yet
real := p
shadow := new(MockResourceProvisioner)
entry.Real = real
entry.Shadow = shadow
}
// Store the value
f.provisioners.SetValue(uid, &entry)
raw = &entry
}
// Read the entry
entry, ok := raw.(*shadowComponentFactoryProvisionerEntry)
if !ok {
return nil, nil, fmt.Errorf("Unknown value for shadow provisioner: %#v", raw)
}
// Return
return entry.Real, entry.Shadow, entry.Err
}

View File

@ -31,16 +31,15 @@ func newShadowContext(c *Context) (*Context, *Context, io.Closer) {
} }
// The factories // The factories
providerFactory := &shadowResourceProviderFactory{Original: c.providers} componentsReal, componentsShadow := newShadowComponentFactory(c.components)
// Create the shadow // Create the shadow
shadow := &Context{ shadow := &Context{
components: componentsShadow,
destroy: c.destroy, destroy: c.destroy,
diff: c.diff.DeepCopy(), diff: c.diff.DeepCopy(),
hooks: nil, // TODO: do we need to copy? stop hook? hooks: nil, // TODO: do we need to copy? stop hook?
module: c.module, module: c.module,
providers: providerFactory.ShadowMap(),
provisioners: nil, //TODO
state: c.state.DeepCopy(), state: c.state.DeepCopy(),
targets: targetRaw.([]string), targets: targetRaw.([]string),
uiInput: nil, // TODO uiInput: nil, // TODO
@ -57,17 +56,17 @@ func newShadowContext(c *Context) (*Context, *Context, io.Closer) {
// the context given except we need to modify some of the values // 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. // to point to the real side of a shadow so the shadow can compare values.
real := *c real := *c
real.providers = providerFactory.RealMap() real.components = componentsReal
return &real, shadow, &shadowContextCloser{ return &real, shadow, &shadowContextCloser{
Providers: providerFactory, Components: componentsShadow,
} }
} }
// shadowContextCloser is the io.Closer returned by newShadowContext that // shadowContextCloser is the io.Closer returned by newShadowContext that
// closes all the shadows and returns the results. // closes all the shadows and returns the results.
type shadowContextCloser struct { type shadowContextCloser struct {
Providers interface{} Components interface{}
} }
// Close closes the shadow context. // Close closes the shadow context.

View File

@ -1,101 +0,0 @@
package terraform
import (
"fmt"
"github.com/hashicorp/terraform/helper/shadow"
)
// shadowResourceProviderFactory is a helper that takes an actual, original
// map of ResourceProvider factories and provides methods to create mappings
// for shadowed resource providers.
type shadowResourceProviderFactory struct {
// Original is the original factory map
Original map[string]ResourceProviderFactory
shadows shadow.KeyedValue
}
type shadowResourceProviderFactoryEntry struct {
Real ResourceProvider
Shadow shadowResourceProvider
Err error
}
// RealMap returns the factory map for the "real" side of the shadow. This
// is the side that does actual work.
// TODO: test
func (f *shadowResourceProviderFactory) RealMap() map[string]ResourceProviderFactory {
m := make(map[string]ResourceProviderFactory)
for k, _ := range f.Original {
m[k] = f.realFactory(k)
}
return m
}
// ShadowMap returns the factory map for the "shadow" side of the shadow. This
// is the side that doesn't do any actual work but does compare results
// with the real side.
// TODO: test
func (f *shadowResourceProviderFactory) ShadowMap() map[string]ResourceProviderFactory {
m := make(map[string]ResourceProviderFactory)
for k, _ := range f.Original {
m[k] = f.shadowFactory(k)
}
return m
}
func (f *shadowResourceProviderFactory) realFactory(n string) ResourceProviderFactory {
return func() (ResourceProvider, error) {
// Get the original factory function
originalF, ok := f.Original[n]
if !ok {
return nil, fmt.Errorf("unknown provider initialized: %s", n)
}
// Build the entry
var entry shadowResourceProviderFactoryEntry
// Initialize it
p, err := originalF()
if err != nil {
entry.Err = err
p = nil // Just to be sure
}
if p != nil {
// Create the shadow
real, shadow := newShadowResourceProvider(p)
entry.Real = real
entry.Shadow = shadow
}
// Store the value
f.shadows.SetValue(n, &entry)
// Return
return entry.Real, entry.Err
}
}
func (f *shadowResourceProviderFactory) shadowFactory(n string) ResourceProviderFactory {
return func() (ResourceProvider, error) {
// Get the value
raw := f.shadows.Value(n)
if raw == nil {
return nil, fmt.Errorf(
"Nil shadow value for provider %q. Please report this bug.",
n)
}
entry, ok := raw.(*shadowResourceProviderFactoryEntry)
if !ok {
return nil, fmt.Errorf("Unknown value for shadow provider: %#v", raw)
}
// Return
return entry.Shadow, entry.Err
}
}