terraform: validate provisioners

This commit is contained in:
Mitchell Hashimoto 2015-02-09 11:15:54 -08:00
parent ea42deb66c
commit d94c4392eb
12 changed files with 251 additions and 65 deletions

View File

@ -26,11 +26,12 @@ type ContextOpts struct {
// perform operations on infrastructure. This structure is built using
// NewContext. See the documentation for that.
type Context2 struct {
module *module.Tree
providers map[string]ResourceProviderFactory
state *State
stateLock sync.RWMutex
variables map[string]string
module *module.Tree
providers map[string]ResourceProviderFactory
provisioners map[string]ResourceProvisionerFactory
state *State
stateLock sync.RWMutex
variables map[string]string
}
// NewContext creates a new Context structure.
@ -40,10 +41,11 @@ type Context2 struct {
// the values themselves.
func NewContext2(opts *ContextOpts) *Context2 {
return &Context2{
module: opts.Module,
providers: opts.Providers,
state: opts.State,
variables: opts.Variables,
module: opts.Module,
providers: opts.Providers,
provisioners: opts.Provisioners,
state: opts.State,
variables: opts.Variables,
}
}
@ -56,10 +58,16 @@ func (c *Context2) GraphBuilder() GraphBuilder {
providers = append(providers, k)
}
provisioners := make([]string, 0, len(c.provisioners))
for k, _ := range c.provisioners {
provisioners = append(provisioners, k)
}
return &BuiltinGraphBuilder{
Root: c.module,
Providers: providers,
State: c.state,
Root: c.module,
Providers: providers,
Provisioners: provisioners,
State: c.state,
}
}

View File

@ -243,6 +243,31 @@ func TestContext2Validate_providerConfig_good(t *testing.T) {
}
}
func TestContext2Validate_provisionerConfig_bad(t *testing.T) {
m := testModule(t, "validate-bad-prov-conf")
p := testProvider("aws")
pr := testProvisioner()
c := testContext2(t, &ContextOpts{
Module: m,
Providers: map[string]ResourceProviderFactory{
"aws": testProviderFuncFixed(p),
},
Provisioners: map[string]ResourceProvisionerFactory{
"shell": testProvisionerFuncFixed(pr),
},
})
pr.ValidateReturnErrors = []error{fmt.Errorf("bad")}
w, e := c.Validate()
if len(w) > 0 {
t.Fatalf("bad: %#v", w)
}
if len(e) == 0 {
t.Fatalf("bad: %#v", e)
}
}
func TestContext2Validate_requiredVar(t *testing.T) {
m := testModule(t, "validate-required-var")
p := testProvider("aws")
@ -512,31 +537,6 @@ func TestContextValidate_tainted(t *testing.T) {
}
}
func TestContextValidate_provisionerConfig_bad(t *testing.T) {
m := testModule(t, "validate-bad-prov-conf")
p := testProvider("aws")
pr := testProvisioner()
c := testContext(t, &ContextOpts{
Module: m,
Providers: map[string]ResourceProviderFactory{
"aws": testProviderFuncFixed(p),
},
Provisioners: map[string]ResourceProvisionerFactory{
"shell": testProvisionerFuncFixed(pr),
},
})
pr.ValidateReturnErrors = []error{fmt.Errorf("bad")}
w, e := c.Validate()
if len(w) > 0 {
t.Fatalf("bad: %#v", w)
}
if len(e) == 0 {
t.Fatalf("bad: %#v", e)
}
}
func TestContextValidate_provisionerConfig_good(t *testing.T) {
m := testModule(t, "validate-bad-prov-conf")
p := testProvider("aws")

View File

@ -19,6 +19,16 @@ type EvalContext interface {
// initialized) or returns nil if the provider isn't initialized.
Provider(string) ResourceProvider
// InitProvisioner initializes the provisioner with the given name and
// returns the implementation of the resource provisioner or an error.
//
// It is an error to initialize the same provisioner more than once.
InitProvisioner(string) (ResourceProvisioner, error)
// Provisioner gets the provisioner instance with the given name (already
// initialized) or returns nil if the provisioner isn't initialized.
Provisioner(string) ResourceProvisioner
// Interpolate takes the given raw configuration and completes
// the interpolations, returning the processed ResourceConfig.
//
@ -39,6 +49,15 @@ type MockEvalContext struct {
ProviderName string
ProviderProvider ResourceProvider
InitProvisionerCalled bool
InitProvisionerName string
InitProvisionerProvisioner ResourceProvisioner
InitProvisionerError error
ProvisionerCalled bool
ProvisionerName string
ProvisionerProvisioner ResourceProvisioner
InterpolateCalled bool
InterpolateConfig *config.RawConfig
InterpolateResource *Resource
@ -61,6 +80,18 @@ func (c *MockEvalContext) Provider(n string) ResourceProvider {
return c.ProviderProvider
}
func (c *MockEvalContext) InitProvisioner(n string) (ResourceProvisioner, error) {
c.InitProvisionerCalled = true
c.InitProvisionerName = n
return c.InitProvisionerProvisioner, c.InitProvisionerError
}
func (c *MockEvalContext) Provisioner(n string) ResourceProvisioner {
c.ProvisionerCalled = true
c.ProvisionerName = n
return c.ProvisionerProvisioner
}
func (c *MockEvalContext) Interpolate(
config *config.RawConfig, resource *Resource) (*ResourceConfig, error) {
c.InterpolateCalled = true

View File

@ -12,11 +12,14 @@ import (
// BuiltinEvalContext is an EvalContext implementation that is used by
// Terraform by default.
type BuiltinEvalContext struct {
PathValue []string
Interpolater *Interpolater
Providers map[string]ResourceProviderFactory
ProviderCache map[string]ResourceProvider
ProviderLock *sync.Mutex
PathValue []string
Interpolater *Interpolater
Providers map[string]ResourceProviderFactory
ProviderCache map[string]ResourceProvider
ProviderLock *sync.Mutex
Provisioners map[string]ResourceProvisionerFactory
ProvisionerCache map[string]ResourceProvisioner
ProvisionerLock *sync.Mutex
once sync.Once
}
@ -57,6 +60,43 @@ func (ctx *BuiltinEvalContext) Provider(n string) ResourceProvider {
return ctx.ProviderCache[ctx.pathCacheKey()]
}
func (ctx *BuiltinEvalContext) InitProvisioner(
n string) (ResourceProvisioner, error) {
ctx.once.Do(ctx.init)
// If we already initialized, it is an error
if p := ctx.Provisioner(n); p != nil {
return nil, fmt.Errorf("Provisioner '%s' already initialized", n)
}
// Warning: make sure to acquire these locks AFTER the call to Provisioner
// above, since it also acquires locks.
ctx.ProvisionerLock.Lock()
defer ctx.ProvisionerLock.Unlock()
f, ok := ctx.Provisioners[n]
if !ok {
return nil, fmt.Errorf("Provisioner '%s' not found", n)
}
p, err := f()
if err != nil {
return nil, err
}
ctx.ProvisionerCache[ctx.pathCacheKey()] = p
return p, nil
}
func (ctx *BuiltinEvalContext) Provisioner(n string) ResourceProvisioner {
ctx.once.Do(ctx.init)
ctx.ProvisionerLock.Lock()
defer ctx.ProvisionerLock.Unlock()
return ctx.ProvisionerCache[ctx.pathCacheKey()]
}
func (ctx *BuiltinEvalContext) Interpolate(
cfg *config.RawConfig, r *Resource) (*ResourceConfig, error) {
if cfg != nil {

View File

@ -0,0 +1,51 @@
package terraform
import (
"fmt"
)
// EvalInitProvisioner is an EvalNode implementation that initializes a provisioner
// and returns nothing. The provisioner can be retrieved again with the
// EvalGetProvisioner node.
type EvalInitProvisioner struct {
Name string
}
func (n *EvalInitProvisioner) Args() ([]EvalNode, []EvalType) {
return nil, nil
}
// TODO: test
func (n *EvalInitProvisioner) Eval(
ctx EvalContext, args []interface{}) (interface{}, error) {
return ctx.InitProvisioner(n.Name)
}
func (n *EvalInitProvisioner) Type() EvalType {
return EvalTypeNull
}
// EvalGetProvisioner is an EvalNode implementation that retrieves an already
// initialized provisioner instance for the given name.
type EvalGetProvisioner struct {
Name string
}
func (n *EvalGetProvisioner) Args() ([]EvalNode, []EvalType) {
return nil, nil
}
// TODO: test
func (n *EvalGetProvisioner) Eval(
ctx EvalContext, args []interface{}) (interface{}, error) {
result := ctx.Provisioner(n.Name)
if result == nil {
return nil, fmt.Errorf("provisioner %s not initialized", n.Name)
}
return result, nil
}
func (n *EvalGetProvisioner) Type() EvalType {
return EvalTypeResourceProvisioner
}

View File

@ -12,8 +12,9 @@ package terraform
type EvalType uint32
const (
EvalTypeInvalid EvalType = 0
EvalTypeNull EvalType = 1 << iota // nil
EvalTypeConfig // *ResourceConfig
EvalTypeResourceProvider // ResourceProvider
EvalTypeInvalid EvalType = 0
EvalTypeNull EvalType = 1 << iota // nil
EvalTypeConfig // *ResourceConfig
EvalTypeResourceProvider // ResourceProvider
EvalTypeResourceProvisioner // ResourceProvisioner
)

View File

@ -91,6 +91,37 @@ func (n *EvalValidateProvider) Type() EvalType {
return EvalTypeNull
}
// EvalValidateProvisioner is an EvalNode implementation that validates
// the configuration of a resource.
type EvalValidateProvisioner struct {
Provisioner EvalNode
Config EvalNode
}
func (n *EvalValidateProvisioner) Args() ([]EvalNode, []EvalType) {
return []EvalNode{n.Provisioner, n.Config},
[]EvalType{EvalTypeResourceProvisioner, EvalTypeConfig}
}
func (n *EvalValidateProvisioner) Eval(
ctx EvalContext, args []interface{}) (interface{}, error) {
provider := args[0].(ResourceProvisioner)
config := args[1].(*ResourceConfig)
warns, errs := provider.Validate(config)
if len(warns) == 0 && len(errs) == 0 {
return nil, nil
}
return nil, &EvalValidateError{
Warnings: warns,
Errors: errs,
}
}
func (n *EvalValidateProvisioner) Type() EvalType {
return EvalTypeNull
}
// EvalValidateResource is an EvalNode implementation that validates
// the configuration of a resource.
type EvalValidateResource struct {

View File

@ -9,6 +9,7 @@ const (
_EvalType_name_1 = "EvalTypeNull"
_EvalType_name_2 = "EvalTypeConfig"
_EvalType_name_3 = "EvalTypeResourceProvider"
_EvalType_name_4 = "EvalTypeResourceProvisioner"
)
var (
@ -16,6 +17,7 @@ var (
_EvalType_index_1 = [...]uint8{0, 12}
_EvalType_index_2 = [...]uint8{0, 14}
_EvalType_index_3 = [...]uint8{0, 24}
_EvalType_index_4 = [...]uint8{0, 27}
)
func (i EvalType) String() string {
@ -28,6 +30,8 @@ func (i EvalType) String() string {
return _EvalType_name_2
case i == 8:
return _EvalType_name_3
case i == 16:
return _EvalType_name_4
default:
return fmt.Sprintf("EvalType(%d)", i)
}

View File

@ -52,6 +52,9 @@ type BuiltinGraphBuilder struct {
// Providers is the list of providers supported.
Providers []string
// Provisioners is the list of provisioners supported.
Provisioners []string
}
// Build builds the graph according to the steps returned by Steps.
@ -78,6 +81,9 @@ func (b *BuiltinGraphBuilder) Steps() []GraphTransformer {
&PruneProviderTransformer{},
// Provisioner-related transformations
&MissingProvisionerTransformer{Provisioners: b.Provisioners},
&ProvisionerTransformer{},
&PruneProvisionerTransformer{},
// Run our vertex-level transforms
&VertexTransformer{

View File

@ -22,20 +22,25 @@ type ContextGraphWalker struct {
ValidationWarnings []string
ValidationErrors []error
errorLock sync.Mutex
once sync.Once
providerCache map[string]ResourceProvider
providerLock sync.Mutex
errorLock sync.Mutex
once sync.Once
providerCache map[string]ResourceProvider
providerLock sync.Mutex
provisionerCache map[string]ResourceProvisioner
provisionerLock sync.Mutex
}
func (w *ContextGraphWalker) EnterGraph(g *Graph) EvalContext {
w.once.Do(w.init)
return &BuiltinEvalContext{
PathValue: g.Path,
Providers: w.Context.providers,
ProviderCache: w.providerCache,
ProviderLock: &w.providerLock,
PathValue: g.Path,
Providers: w.Context.providers,
ProviderCache: w.providerCache,
ProviderLock: &w.providerLock,
Provisioners: w.Context.provisioners,
ProvisionerCache: w.provisionerCache,
ProvisionerLock: &w.provisionerLock,
Interpolater: &Interpolater{
Operation: w.Operation,
Module: w.Context.module,
@ -72,4 +77,5 @@ func (w *ContextGraphWalker) ExitEvalTree(
func (w *ContextGraphWalker) init() {
w.providerCache = make(map[string]ResourceProvider, 5)
w.provisionerCache = make(map[string]ResourceProvisioner, 5)
}

View File

@ -104,8 +104,7 @@ func (n *graphNodeMissingProvisioner) Name() string {
// GraphNodeEvalable impl.
func (n *graphNodeMissingProvisioner) EvalTree() EvalNode {
return nil
//return ProvisionerEvalTree(n.ProvisionerNameValue, nil)
return &EvalInitProvisioner{Name: n.ProvisionerNameValue}
}
func (n *graphNodeMissingProvisioner) ProvisionerName() string {

View File

@ -78,14 +78,23 @@ func (n *graphNodeExpandedResource) ProvidedBy() string {
// GraphNodeEvalable impl.
func (n *graphNodeExpandedResource) EvalTree() EvalNode {
return &EvalSequence{
Nodes: []EvalNode{
&EvalValidateResource{
Provider: &EvalGetProvider{Name: n.ProvidedBy()},
Config: &EvalInterpolate{Config: n.Resource.RawConfig},
ResourceName: n.Resource.Name,
ResourceType: n.Resource.Type,
},
},
seq := &EvalSequence{Nodes: make([]EvalNode, 0, 5)}
// Validate the resource
seq.Nodes = append(seq.Nodes, &EvalValidateResource{
Provider: &EvalGetProvider{Name: n.ProvidedBy()},
Config: &EvalInterpolate{Config: n.Resource.RawConfig},
ResourceName: n.Resource.Name,
ResourceType: n.Resource.Type,
})
// Validate all the provisioners
for _, p := range n.Resource.Provisioners {
seq.Nodes = append(seq.Nodes, &EvalValidateProvisioner{
Provisioner: &EvalGetProvisioner{Name: p.Type},
Config: &EvalInterpolate{Config: p.RawConfig},
})
}
return seq
}