terraform: validate provisioners
This commit is contained in:
parent
ea42deb66c
commit
d94c4392eb
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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{
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue