terraform: simplify semantic checks out into sep functions

This commit is contained in:
Mitchell Hashimoto 2014-06-05 11:53:07 -07:00
parent 9018beda81
commit 02fde14fb6
3 changed files with 150 additions and 91 deletions

134
terraform/semantics.go Normal file
View File

@ -0,0 +1,134 @@
package terraform
import (
"fmt"
"strings"
"github.com/hashicorp/terraform/config"
)
// smcProviders matches up the resources with a provider
// those providers and does the initial `Configure` on the provider.
func smcProviders(c *Config) (map[*config.Resource]ResourceProvider, []error) {
var errs []error
// Keep track of providers we know we couldn't instantiate so
// that we don't get a ton of errors about the same provider.
failures := make(map[string]struct{})
// Go through each resource and match it up to a provider
mapping := make(map[*config.Resource]ResourceProvider)
providers := make(map[string]ResourceProvider)
ResourceLoop:
for _, r := range c.Config.Resources {
// Find the prefixes that match this in the order of
// longest matching first (most specific)
prefixes := matchingPrefixes(r.Type, c.Providers)
if len(prefixes) > 0 {
if _, ok := failures[prefixes[0]]; ok {
// We already failed this provider, meaning this
// resource will never succeed, so just continue.
continue
}
}
// Go through each prefix and instantiate if necessary, then
// verify if this provider is of use to us or not.
var provider ResourceProvider = nil
for _, prefix := range prefixes {
// Initialize the provider
p, ok := providers[prefix]
if !ok {
var err error
p, err = c.Providers[prefix]()
if err != nil {
errs = append(errs, fmt.Errorf(
"Error instantiating resource provider for "+
"prefix %s: %s", prefix, err))
// Record the error so that we don't check it again
failures[prefix] = struct{}{}
// Jump to the next resource
continue ResourceLoop
}
providers[prefix] = p
}
// Test if this provider matches what we need
if !ProviderSatisfies(p, r.Type) {
continue
}
// A match! Set it and break
provider = p
break
}
if provider == nil {
// We never found a matching provider.
errs = append(errs, fmt.Errorf(
"Provider for resource %s not found.",
r.Id()))
}
mapping[r] = provider
}
if len(errs) > 0 {
return nil, errs
}
return mapping, nil
}
// smcVariables does all the semantic checks to verify that the
// variables given in the configuration to instantiate a Terraform
// struct are valid.
func smcVariables(c *Config) []error {
var errs []error
// Check that all required variables are present
required := make(map[string]struct{})
for k, v := range c.Config.Variables {
if v.Required() {
required[k] = struct{}{}
}
}
for k, _ := range c.Variables {
delete(required, k)
}
if len(required) > 0 {
for k, _ := range required {
errs = append(errs, fmt.Errorf(
"Required variable not set: %s", k))
}
}
// TODO(mitchellh): variables that are unknown
return errs
}
// matchingPrefixes takes a resource type and a set of resource
// providers we know about by prefix and returns a list of prefixes
// that might be valid for that resource.
//
// The list returned is in the order that they should be attempted.
func matchingPrefixes(
t string,
ps map[string]ResourceProviderFactory) []string {
result := make([]string, 0, 1)
for prefix, _ := range ps {
if strings.HasPrefix(t, prefix) {
result = append(result, prefix)
}
}
// TODO(mitchellh): Order by longest prefix first
return result
}

View File

@ -2,7 +2,6 @@ package terraform
import ( import (
"fmt" "fmt"
"strings"
"sync" "sync"
"github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/config"
@ -37,68 +36,14 @@ func New(c *Config) (*Terraform, error) {
var errs []error var errs []error
// Validate that all required variables have values // Validate that all required variables have values
required := make(map[string]struct{}) if err := smcVariables(c); err != nil {
for k, v := range c.Config.Variables { errs = append(errs, err...)
if v.Required() {
required[k] = struct{}{}
}
}
for k, _ := range c.Variables {
delete(required, k)
}
if len(required) > 0 {
for k, _ := range required {
errs = append(errs, fmt.Errorf(
"Required variable not set: %s", k))
}
} }
// TODO(mitchellh): variables that are unknown // Match all the resources with a provider and initialize the providers
mapping, err := smcProviders(c)
// Go through each resource and match it up to a provider if err != nil {
mapping := make(map[*config.Resource]ResourceProvider) errs = append(errs, err...)
providers := make(map[string]ResourceProvider)
for _, r := range c.Config.Resources {
// Find the prefixes that match this in the order of
// longest matching first (most specific)
prefixes := matchingPrefixes(r.Type, c.Providers)
// Go through each prefix and instantiate if necessary, then
// verify if this provider is of use to us or not.
var provider ResourceProvider = nil
for _, prefix := range prefixes {
p, ok := providers[prefix]
if !ok {
var err error
p, err = c.Providers[prefix]()
if err != nil {
err = fmt.Errorf(
"Error instantiating resource provider for "+
"prefix %s: %s", prefix, err)
return nil, err
}
providers[prefix] = p
}
// Test if this provider matches what we need
if !ProviderSatisfies(p, r.Type) {
continue
}
// A match! Set it and break
provider = p
break
}
if provider == nil {
// We never found a matching provider.
errs = append(errs, fmt.Errorf(
"Provider for resource %s not found.",
r.Id()))
}
mapping[r] = provider
} }
// Build the resource graph // Build the resource graph
@ -203,23 +148,3 @@ func (t *Terraform) diffWalkFn(
return nil return nil
} }
} }
// matchingPrefixes takes a resource type and a set of resource
// providers we know about by prefix and returns a list of prefixes
// that might be valid for that resource.
//
// The list returned is in the order that they should be attempted.
func matchingPrefixes(
t string,
ps map[string]ResourceProviderFactory) []string {
result := make([]string, 0, 1)
for prefix, _ := range ps {
if strings.HasPrefix(t, prefix) {
result = append(result, prefix)
}
}
// TODO(mitchellh): Order by longest prefix first
return result
}

View File

@ -41,17 +41,17 @@ func TestNew(t *testing.T) {
} }
/* /*
val := testProviderMock(mapping["aws_instance.foo"]). val := testProviderMock(mapping["aws_instance.foo"]).
ConfigureCommonConfig.TFComputedPlaceholder ConfigureCommonConfig.TFComputedPlaceholder
if val == "" { if val == "" {
t.Fatal("should have computed placeholder") t.Fatal("should have computed placeholder")
} }
val = testProviderMock(mapping["aws_instance.bar"]). val = testProviderMock(mapping["aws_instance.bar"]).
ConfigureCommonConfig.TFComputedPlaceholder ConfigureCommonConfig.TFComputedPlaceholder
if val == "" { if val == "" {
t.Fatal("should have computed placeholder") t.Fatal("should have computed placeholder")
} }
*/ */
} }