From 02fde14fb6e8142c283fa2ff2d3649f19dfa5c27 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 5 Jun 2014 11:53:07 -0700 Subject: [PATCH] terraform: simplify semantic checks out into sep functions --- terraform/semantics.go | 134 ++++++++++++++++++++++++++++++++++++ terraform/terraform.go | 87 ++--------------------- terraform/terraform_test.go | 20 +++--- 3 files changed, 150 insertions(+), 91 deletions(-) create mode 100644 terraform/semantics.go diff --git a/terraform/semantics.go b/terraform/semantics.go new file mode 100644 index 000000000..2499bb6f7 --- /dev/null +++ b/terraform/semantics.go @@ -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 +} diff --git a/terraform/terraform.go b/terraform/terraform.go index 5b1ecf35f..de91e307f 100644 --- a/terraform/terraform.go +++ b/terraform/terraform.go @@ -2,7 +2,6 @@ package terraform import ( "fmt" - "strings" "sync" "github.com/hashicorp/terraform/config" @@ -37,68 +36,14 @@ func New(c *Config) (*Terraform, error) { var errs []error // Validate that all required variables have values - 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)) - } + if err := smcVariables(c); err != nil { + errs = append(errs, err...) } - // TODO(mitchellh): variables that are unknown - - // Go through each resource and match it up to a provider - mapping := make(map[*config.Resource]ResourceProvider) - 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 + // Match all the resources with a provider and initialize the providers + mapping, err := smcProviders(c) + if err != nil { + errs = append(errs, err...) } // Build the resource graph @@ -203,23 +148,3 @@ func (t *Terraform) diffWalkFn( 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 -} diff --git a/terraform/terraform_test.go b/terraform/terraform_test.go index 513d8a215..ea9d5d5dd 100644 --- a/terraform/terraform_test.go +++ b/terraform/terraform_test.go @@ -41,17 +41,17 @@ func TestNew(t *testing.T) { } /* - val := testProviderMock(mapping["aws_instance.foo"]). - ConfigureCommonConfig.TFComputedPlaceholder - if val == "" { - t.Fatal("should have computed placeholder") - } + val := testProviderMock(mapping["aws_instance.foo"]). + ConfigureCommonConfig.TFComputedPlaceholder + if val == "" { + t.Fatal("should have computed placeholder") + } - val = testProviderMock(mapping["aws_instance.bar"]). - ConfigureCommonConfig.TFComputedPlaceholder - if val == "" { - t.Fatal("should have computed placeholder") - } + val = testProviderMock(mapping["aws_instance.bar"]). + ConfigureCommonConfig.TFComputedPlaceholder + if val == "" { + t.Fatal("should have computed placeholder") + } */ }