From 947fa4e669b8baf6cb3b7a6e79f012d5b55bf176 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 3 Jul 2014 10:14:17 -0700 Subject: [PATCH] terraform: Context introduction --- config/config.go | 4 +- .../multierror/error.go | 24 +++---- .../multierror/error_test.go | 26 +++---- terraform/context.go | 69 +++++++++++++++++++ terraform/context_test.go | 54 +++++++++++++++ terraform/graph.go | 7 +- terraform/multi_error.go | 50 -------------- terraform/multi_error_test.go | 56 --------------- terraform/terraform.go | 3 +- .../test-fixtures/validate-bad-var/main.tf | 7 ++ terraform/test-fixtures/validate-good/main.tf | 7 ++ .../validate-required-var/main.tf | 5 ++ 12 files changed, 176 insertions(+), 136 deletions(-) rename config/multi_error.go => helper/multierror/error.go (54%) rename config/multi_error_test.go => helper/multierror/error_test.go (53%) create mode 100644 terraform/context.go create mode 100644 terraform/context_test.go delete mode 100644 terraform/multi_error.go delete mode 100644 terraform/multi_error_test.go create mode 100644 terraform/test-fixtures/validate-bad-var/main.tf create mode 100644 terraform/test-fixtures/validate-good/main.tf create mode 100644 terraform/test-fixtures/validate-required-var/main.tf diff --git a/config/config.go b/config/config.go index bab8a6322..8c8b80fa5 100644 --- a/config/config.go +++ b/config/config.go @@ -5,6 +5,8 @@ package config import ( "fmt" "strings" + + "github.com/hashicorp/terraform/helper/multierror" ) // Config is the configuration that comes from loading a collection @@ -131,7 +133,7 @@ func (c *Config) Validate() error { } if len(errs) > 0 { - return &MultiError{Errors: errs} + return &multierror.Error{Errors: errs} } return nil diff --git a/config/multi_error.go b/helper/multierror/error.go similarity index 54% rename from config/multi_error.go rename to helper/multierror/error.go index 86a75d163..cddd2fc84 100644 --- a/config/multi_error.go +++ b/helper/multierror/error.go @@ -1,18 +1,18 @@ -package config +package multierror import ( "fmt" "strings" ) -// MultiError is an error type to track multiple errors. This is used to +// Error is an error type to track multiple errors. This is used to // accumulate errors in cases such as configuration parsing, and returning // them as a single error. -type MultiError struct { +type Error struct { Errors []error } -func (e *MultiError) Error() string { +func (e *Error) Error() string { points := make([]string, len(e.Errors)) for i, err := range e.Errors { points[i] = fmt.Sprintf("* %s", err) @@ -23,18 +23,18 @@ func (e *MultiError) Error() string { len(e.Errors), strings.Join(points, "\n")) } -// MultiErrorAppend is a helper function that will append more errors -// onto a MultiError in order to create a larger multi-error. If the -// original error is not a MultiError, it will be turned into one. -func MultiErrorAppend(err error, errs ...error) *MultiError { +// ErrorAppend is a helper function that will append more errors +// onto a Error in order to create a larger multi-error. If the +// original error is not a Error, it will be turned into one. +func ErrorAppend(err error, errs ...error) *Error { if err == nil { - err = new(MultiError) + err = new(Error) } switch err := err.(type) { - case *MultiError: + case *Error: if err == nil { - err = new(MultiError) + err = new(Error) } err.Errors = append(err.Errors, errs...) @@ -43,7 +43,7 @@ func MultiErrorAppend(err error, errs ...error) *MultiError { newErrs := make([]error, len(errs)+1) newErrs[0] = err copy(newErrs[1:], errs) - return &MultiError{ + return &Error{ Errors: newErrs, } } diff --git a/config/multi_error_test.go b/helper/multierror/error_test.go similarity index 53% rename from config/multi_error_test.go rename to helper/multierror/error_test.go index 426a818bc..207c00465 100644 --- a/config/multi_error_test.go +++ b/helper/multierror/error_test.go @@ -1,19 +1,19 @@ -package config +package multierror import ( "errors" "testing" ) -func TestMultiError_Impl(t *testing.T) { +func TestError_Impl(t *testing.T) { var raw interface{} - raw = &MultiError{} + raw = &Error{} if _, ok := raw.(error); !ok { - t.Fatal("MultiError must implement error") + t.Fatal("Error must implement error") } } -func TestMultiErrorError(t *testing.T) { +func TestErrorError(t *testing.T) { expected := `2 error(s) occurred: * foo @@ -24,32 +24,32 @@ func TestMultiErrorError(t *testing.T) { errors.New("bar"), } - multi := &MultiError{errors} + multi := &Error{errors} if multi.Error() != expected { t.Fatalf("bad: %s", multi.Error()) } } -func TestMultiErrorAppend_MultiError(t *testing.T) { - original := &MultiError{ +func TestErrorAppend_Error(t *testing.T) { + original := &Error{ Errors: []error{errors.New("foo")}, } - result := MultiErrorAppend(original, errors.New("bar")) + result := ErrorAppend(original, errors.New("bar")) if len(result.Errors) != 2 { t.Fatalf("wrong len: %d", len(result.Errors)) } - original = &MultiError{} - result = MultiErrorAppend(original, errors.New("bar")) + original = &Error{} + result = ErrorAppend(original, errors.New("bar")) if len(result.Errors) != 1 { t.Fatalf("wrong len: %d", len(result.Errors)) } } -func TestMultiErrorAppend_NonMultiError(t *testing.T) { +func TestErrorAppend_NonError(t *testing.T) { original := errors.New("foo") - result := MultiErrorAppend(original, errors.New("bar")) + result := ErrorAppend(original, errors.New("bar")) if len(result.Errors) != 2 { t.Fatalf("wrong len: %d", len(result.Errors)) } diff --git a/terraform/context.go b/terraform/context.go new file mode 100644 index 000000000..1f322d880 --- /dev/null +++ b/terraform/context.go @@ -0,0 +1,69 @@ +package terraform + +import ( + "github.com/hashicorp/terraform/config" + "github.com/hashicorp/terraform/helper/multierror" +) + +// Context represents all the context that Terraform needs in order to +// perform operations on infrastructure. This structure is built using +// ContextOpts and NewContext. See the documentation for those. +// +// Additionally, a context can be created from a Plan using Plan.Context. +type Context struct { + config *config.Config + diff *Diff + hooks []Hook + state *State + providers map[string]ResourceProviderFactory + variables map[string]string +} + +// ContextOpts are the user-creatable configuration structure to create +// a context with NewContext. +type ContextOpts struct { + Config *config.Config + Diff *Diff + Hooks []Hook + State *State + Providers map[string]ResourceProviderFactory + Variables map[string]string +} + +// NewContext creates a new context. +// +// Once a context is created, the pointer values within ContextOpts should +// not be mutated in any way, since the pointers are copied, not the values +// themselves. +func NewContext(opts *ContextOpts) *Context { + return &Context{ + config: opts.Config, + diff: opts.Diff, + hooks: opts.Hooks, + state: opts.State, + providers: opts.Providers, + variables: opts.Variables, + } +} + +// Validate validates the configuration and returns any warnings or errors. +func (c *Context) Validate() ([]string, []error) { + var rerr *multierror.Error + + // Validate the configuration itself + if err := c.config.Validate(); err != nil { + rerr = multierror.ErrorAppend(rerr, err) + } + + // Validate the user variables + if errs := smcUserVariables(c.config, c.variables); len(errs) > 0 { + rerr = multierror.ErrorAppend(rerr, errs...) + } + + var errs []error + if rerr != nil && len(rerr.Errors) > 0 { + errs = rerr.Errors + } + + return nil, errs +} diff --git a/terraform/context_test.go b/terraform/context_test.go new file mode 100644 index 000000000..a82fda892 --- /dev/null +++ b/terraform/context_test.go @@ -0,0 +1,54 @@ +package terraform + +import ( + "testing" +) + +func TestContextValidate(t *testing.T) { + config := testConfig(t, "validate-good") + c := testContext(t, &ContextOpts{ + Config: config, + }) + + w, e := c.Validate() + if len(w) > 0 { + t.Fatalf("bad: %#v", w) + } + if len(e) > 0 { + t.Fatalf("bad: %#v", e) + } +} + +func TestContextValidate_badVar(t *testing.T) { + config := testConfig(t, "validate-bad-var") + c := testContext(t, &ContextOpts{ + Config: config, + }) + + w, e := c.Validate() + if len(w) > 0 { + t.Fatalf("bad: %#v", w) + } + if len(e) == 0 { + t.Fatalf("bad: %#v", e) + } +} + +func TestContextValidate_requiredVar(t *testing.T) { + config := testConfig(t, "validate-required-var") + c := testContext(t, &ContextOpts{ + Config: config, + }) + + w, e := c.Validate() + if len(w) > 0 { + t.Fatalf("bad: %#v", w) + } + if len(e) == 0 { + t.Fatalf("bad: %#v", e) + } +} + +func testContext(t *testing.T, opts *ContextOpts) *Context { + return NewContext(opts) +} diff --git a/terraform/graph.go b/terraform/graph.go index 4380ce751..486996cc4 100644 --- a/terraform/graph.go +++ b/terraform/graph.go @@ -8,6 +8,7 @@ import ( "github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/depgraph" + "github.com/hashicorp/terraform/helper/multierror" ) // GraphOpts are options used to create the resource graph that Terraform @@ -325,7 +326,7 @@ func graphAddMissingResourceProviders( } if len(errs) > 0 { - return &MultiError{Errors: errs} + return &multierror.Error{Errors: errs} } return nil @@ -518,7 +519,7 @@ func graphInitResourceProviders( } if len(errs) > 0 { - return &MultiError{Errors: errs} + return &multierror.Error{Errors: errs} } return nil @@ -583,7 +584,7 @@ func graphMapResourceProviders(g *depgraph.Graph) error { } if len(errs) > 0 { - return &MultiError{Errors: errs} + return &multierror.Error{Errors: errs} } return nil diff --git a/terraform/multi_error.go b/terraform/multi_error.go deleted file mode 100644 index 9f11b95d8..000000000 --- a/terraform/multi_error.go +++ /dev/null @@ -1,50 +0,0 @@ -package terraform - -import ( - "fmt" - "strings" -) - -// MultiError is an error type to track multiple errors. This is used to -// accumulate errors in cases such as configuration parsing, and returning -// them as a single error. -type MultiError struct { - Errors []error -} - -func (e *MultiError) Error() string { - points := make([]string, len(e.Errors)) - for i, err := range e.Errors { - points[i] = fmt.Sprintf("* %s", err) - } - - return fmt.Sprintf( - "%d error(s) occurred:\n\n%s", - len(e.Errors), strings.Join(points, "\n")) -} - -// MultiErrorAppend is a helper function that will append more errors -// onto a MultiError in order to create a larger multi-error. If the -// original error is not a MultiError, it will be turned into one. -func MultiErrorAppend(err error, errs ...error) *MultiError { - if err == nil { - err = new(MultiError) - } - - switch err := err.(type) { - case *MultiError: - if err == nil { - err = new(MultiError) - } - - err.Errors = append(err.Errors, errs...) - return err - default: - newErrs := make([]error, len(errs)+1) - newErrs[0] = err - copy(newErrs[1:], errs) - return &MultiError{ - Errors: newErrs, - } - } -} diff --git a/terraform/multi_error_test.go b/terraform/multi_error_test.go deleted file mode 100644 index 44e07babf..000000000 --- a/terraform/multi_error_test.go +++ /dev/null @@ -1,56 +0,0 @@ -package terraform - -import ( - "errors" - "testing" -) - -func TestMultiError_Impl(t *testing.T) { - var raw interface{} - raw = &MultiError{} - if _, ok := raw.(error); !ok { - t.Fatal("MultiError must implement error") - } -} - -func TestMultiErrorError(t *testing.T) { - expected := `2 error(s) occurred: - -* foo -* bar` - - errors := []error{ - errors.New("foo"), - errors.New("bar"), - } - - multi := &MultiError{errors} - if multi.Error() != expected { - t.Fatalf("bad: %s", multi.Error()) - } -} - -func TestMultiErrorAppend_MultiError(t *testing.T) { - original := &MultiError{ - Errors: []error{errors.New("foo")}, - } - - result := MultiErrorAppend(original, errors.New("bar")) - if len(result.Errors) != 2 { - t.Fatalf("wrong len: %d", len(result.Errors)) - } - - original = &MultiError{} - result = MultiErrorAppend(original, errors.New("bar")) - if len(result.Errors) != 1 { - t.Fatalf("wrong len: %d", len(result.Errors)) - } -} - -func TestMultiErrorAppend_NonMultiError(t *testing.T) { - original := errors.New("foo") - result := MultiErrorAppend(original, errors.New("bar")) - if len(result.Errors) != 2 { - t.Fatalf("wrong len: %d", len(result.Errors)) - } -} diff --git a/terraform/terraform.go b/terraform/terraform.go index 5b4fe60c5..f03602d76 100644 --- a/terraform/terraform.go +++ b/terraform/terraform.go @@ -8,6 +8,7 @@ import ( "github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/depgraph" + "github.com/hashicorp/terraform/helper/multierror" ) // Terraform is the primary structure that is used to interact with @@ -282,7 +283,7 @@ func (t *Terraform) applyWalkFn( // Determine the new state and update variables err = nil if len(errs) > 0 { - err = &MultiError{Errors: errs} + err = &multierror.Error{Errors: errs} } return r.Vars(), err diff --git a/terraform/test-fixtures/validate-bad-var/main.tf b/terraform/test-fixtures/validate-bad-var/main.tf new file mode 100644 index 000000000..f5c9c684f --- /dev/null +++ b/terraform/test-fixtures/validate-bad-var/main.tf @@ -0,0 +1,7 @@ +resource "aws_instance" "foo" { + num = "2" +} + +resource "aws_instance" "bar" { + foo = "${var.foo}" +} diff --git a/terraform/test-fixtures/validate-good/main.tf b/terraform/test-fixtures/validate-good/main.tf new file mode 100644 index 000000000..ce5654441 --- /dev/null +++ b/terraform/test-fixtures/validate-good/main.tf @@ -0,0 +1,7 @@ +resource "aws_instance" "foo" { + num = "2" +} + +resource "aws_instance" "bar" { + foo = "bar" +} diff --git a/terraform/test-fixtures/validate-required-var/main.tf b/terraform/test-fixtures/validate-required-var/main.tf new file mode 100644 index 000000000..a7907b654 --- /dev/null +++ b/terraform/test-fixtures/validate-required-var/main.tf @@ -0,0 +1,5 @@ +variable "foo" {} + +resource "aws_instance" "web" { + ami = "${var.foo}" +}