From 15c0b25d011f37e7c20aeca9eaf461f78285b8d9 Mon Sep 17 00:00:00 2001 From: Alex Pilon Date: Fri, 22 Feb 2019 18:24:37 -0500 Subject: deps: github.com/hashicorp/terraform@sdk-v0.11-with-go-modules Updated via: go get github.com/hashicorp/terraform@sdk-v0.11-with-go-modules and go mod tidy --- .../hashicorp/terraform/helper/resource/testing.go | 348 +++++++++++++++++---- 1 file changed, 285 insertions(+), 63 deletions(-) (limited to 'vendor/github.com/hashicorp/terraform/helper/resource/testing.go') diff --git a/vendor/github.com/hashicorp/terraform/helper/resource/testing.go b/vendor/github.com/hashicorp/terraform/helper/resource/testing.go index d7de1a0..b97673f 100644 --- a/vendor/github.com/hashicorp/terraform/helper/resource/testing.go +++ b/vendor/github.com/hashicorp/terraform/helper/resource/testing.go @@ -11,11 +11,13 @@ import ( "reflect" "regexp" "strings" + "syscall" "testing" "github.com/davecgh/go-spew/spew" - "github.com/hashicorp/go-getter" + "github.com/hashicorp/errwrap" "github.com/hashicorp/go-multierror" + "github.com/hashicorp/logutils" "github.com/hashicorp/terraform/config/module" "github.com/hashicorp/terraform/helper/logging" "github.com/hashicorp/terraform/terraform" @@ -186,6 +188,10 @@ type TestCheckFunc func(*terraform.State) error // ImportStateCheckFunc is the check function for ImportState tests type ImportStateCheckFunc func([]*terraform.InstanceState) error +// ImportStateIdFunc is an ID generation function to help with complex ID +// generation for ImportState tests. +type ImportStateIdFunc func(*terraform.State) (string, error) + // TestCase is a single acceptance test case used to test the apply/destroy // lifecycle of a resource in a specific configuration. // @@ -260,6 +266,15 @@ type TestStep struct { // below. PreConfig func() + // Taint is a list of resource addresses to taint prior to the execution of + // the step. Be sure to only include this at a step where the referenced + // address will be present in state, as it will fail the test if the resource + // is missing. + // + // This option is ignored on ImportState tests, and currently only works for + // resources in the root module path. + Taint []string + //--------------------------------------------------------------- // Test modes. One of the following groups of settings must be // set to determine what the test step will do. Ideally we would've @@ -304,10 +319,19 @@ type TestStep struct { // no-op plans PlanOnly bool + // PreventDiskCleanup can be set to true for testing terraform modules which + // require access to disk at runtime. Note that this will leave files in the + // temp folder + PreventDiskCleanup bool + // PreventPostDestroyRefresh can be set to true for cases where data sources // are tested alongside real resources PreventPostDestroyRefresh bool + // SkipFunc is called before applying config, but after PreConfig + // This is useful for defining test steps with platform-dependent checks + SkipFunc func() (bool, error) + //--------------------------------------------------------------- // ImportState testing //--------------------------------------------------------------- @@ -329,6 +353,12 @@ type TestStep struct { // the unset ImportStateId field. ImportStateIdPrefix string + // ImportStateIdFunc is a function that can be used to dynamically generate + // the ID for the ImportState tests. It is sent the state, which can be + // checked to derive the attributes necessary and generate the string in the + // desired format. + ImportStateIdFunc ImportStateIdFunc + // ImportStateCheck checks the results of ImportState. It should be // used to verify that the resulting value of ImportState has the // proper resources, IDs, and attributes. @@ -345,6 +375,60 @@ type TestStep struct { ImportStateVerifyIgnore []string } +// Set to a file mask in sprintf format where %s is test name +const EnvLogPathMask = "TF_LOG_PATH_MASK" + +func LogOutput(t TestT) (logOutput io.Writer, err error) { + logOutput = ioutil.Discard + + logLevel := logging.LogLevel() + if logLevel == "" { + return + } + + logOutput = os.Stderr + + if logPath := os.Getenv(logging.EnvLogFile); logPath != "" { + var err error + logOutput, err = os.OpenFile(logPath, syscall.O_CREAT|syscall.O_RDWR|syscall.O_APPEND, 0666) + if err != nil { + return nil, err + } + } + + if logPathMask := os.Getenv(EnvLogPathMask); logPathMask != "" { + // Escape special characters which may appear if we have subtests + testName := strings.Replace(t.Name(), "/", "__", -1) + + logPath := fmt.Sprintf(logPathMask, testName) + var err error + logOutput, err = os.OpenFile(logPath, syscall.O_CREAT|syscall.O_RDWR|syscall.O_APPEND, 0666) + if err != nil { + return nil, err + } + } + + // This was the default since the beginning + logOutput = &logutils.LevelFilter{ + Levels: logging.ValidLevels, + MinLevel: logutils.LogLevel(logLevel), + Writer: logOutput, + } + + return +} + +// ParallelTest performs an acceptance test on a resource, allowing concurrency +// with other ParallelTest. +// +// Tests will fail if they do not properly handle conditions to allow multiple +// tests to occur against the same resource or service (e.g. random naming). +// All other requirements of the Test function also apply to this function. +func ParallelTest(t TestT, c TestCase) { + t.Parallel() + Test(t, c) +} + // Test performs an acceptance test on a resource. // // Tests are not run unless an environmental variable "TF_ACC" is @@ -366,7 +450,7 @@ func Test(t TestT, c TestCase) { return } - logWriter, err := logging.LogOutput() + logWriter, err := LogOutput(t) if err != nil { t.Error(fmt.Errorf("error setting up logging: %s", err)) } @@ -398,7 +482,18 @@ func Test(t TestT, c TestCase) { errored := false for i, step := range c.Steps { var err error - log.Printf("[WARN] Test: Executing step %d", i) + log.Printf("[DEBUG] Test: Executing step %d", i) + + if step.SkipFunc != nil { + skip, err := step.SkipFunc() + if err != nil { + t.Fatal(err) + } + if skip { + log.Printf("[WARN] Skipping step %d", i) + continue + } + } if step.Config == "" && !step.ImportState { err = fmt.Errorf( @@ -418,6 +513,15 @@ func Test(t TestT, c TestCase) { } } + // If we expected an error, but did not get one, fail + if err == nil && step.ExpectError != nil { + errored = true + t.Error(fmt.Sprintf( + "Step %d, no error received, but expected a match to:\n\n%s\n\n", + i, step.ExpectError)) + break + } + // If there was an error, exit if err != nil { // Perhaps we expected an error? Check if it matches @@ -485,6 +589,7 @@ func Test(t TestT, c TestCase) { Config: lastStep.Config, Check: c.CheckDestroy, Destroy: true, + PreventDiskCleanup: lastStep.PreventDiskCleanup, PreventPostDestroyRefresh: c.PreventPostDestroyRefresh, } @@ -593,18 +698,12 @@ func testIDOnlyRefresh(c TestCase, opts terraform.ContextOpts, step TestStep, r if err != nil { return err } - if ws, es := ctx.Validate(); len(ws) > 0 || len(es) > 0 { - if len(es) > 0 { - estrs := make([]string, len(es)) - for i, e := range es { - estrs[i] = e.Error() - } - return fmt.Errorf( - "Configuration is invalid.\n\nWarnings: %#v\n\nErrors: %#v", - ws, estrs) + if diags := ctx.Validate(); len(diags) > 0 { + if diags.HasErrors() { + return errwrap.Wrapf("config is invalid: {{err}}", diags.Err()) } - log.Printf("[WARN] Config warnings: %#v", ws) + log.Printf("[WARN] Config warnings:\n%s", diags.Err().Error()) } // Refresh! @@ -657,9 +756,7 @@ func testIDOnlyRefresh(c TestCase, opts terraform.ContextOpts, step TestStep, r return nil } -func testModule( - opts terraform.ContextOpts, - step TestStep) (*module.Tree, error) { +func testModule(opts terraform.ContextOpts, step TestStep) (*module.Tree, error) { if step.PreConfig != nil { step.PreConfig() } @@ -669,7 +766,12 @@ func testModule( return nil, fmt.Errorf( "Error creating temporary directory for config: %s", err) } - defer os.RemoveAll(cfgPath) + + if step.PreventDiskCleanup { + log.Printf("[INFO] Skipping defer os.RemoveAll call") + } else { + defer os.RemoveAll(cfgPath) + } // Write the configuration cfgF, err := os.Create(filepath.Join(cfgPath, "main.tf")) @@ -693,10 +795,11 @@ func testModule( } // Load the modules - modStorage := &getter.FolderStorage{ + modStorage := &module.Storage{ StorageDir: filepath.Join(cfgPath, ".tfmodules"), + Mode: module.GetModeGet, } - err = mod.Load(modStorage, module.GetModeGet) + err = mod.Load(modStorage) if err != nil { return nil, fmt.Errorf("Error downloading modules: %s", err) } @@ -771,12 +874,29 @@ func TestCheckResourceAttrSet(name, key string) TestCheckFunc { return err } - if val, ok := is.Attributes[key]; ok && val != "" { - return nil + return testCheckResourceAttrSet(is, name, key) + } +} + +// TestCheckModuleResourceAttrSet - as per TestCheckResourceAttrSet but with +// support for non-root modules +func TestCheckModuleResourceAttrSet(mp []string, name string, key string) TestCheckFunc { + return func(s *terraform.State) error { + is, err := modulePathPrimaryInstanceState(s, mp, name) + if err != nil { + return err } + return testCheckResourceAttrSet(is, name, key) + } +} + +func testCheckResourceAttrSet(is *terraform.InstanceState, name string, key string) error { + if val, ok := is.Attributes[key]; !ok || val == "" { return fmt.Errorf("%s: Attribute '%s' expected to be set", name, key) } + + return nil } // TestCheckResourceAttr is a TestCheckFunc which validates @@ -788,21 +908,37 @@ func TestCheckResourceAttr(name, key, value string) TestCheckFunc { return err } - if v, ok := is.Attributes[key]; !ok || v != value { - if !ok { - return fmt.Errorf("%s: Attribute '%s' not found", name, key) - } + return testCheckResourceAttr(is, name, key, value) + } +} - return fmt.Errorf( - "%s: Attribute '%s' expected %#v, got %#v", - name, - key, - value, - v) +// TestCheckModuleResourceAttr - as per TestCheckResourceAttr but with +// support for non-root modules +func TestCheckModuleResourceAttr(mp []string, name string, key string, value string) TestCheckFunc { + return func(s *terraform.State) error { + is, err := modulePathPrimaryInstanceState(s, mp, name) + if err != nil { + return err } - return nil + return testCheckResourceAttr(is, name, key, value) + } +} + +func testCheckResourceAttr(is *terraform.InstanceState, name string, key string, value string) error { + if v, ok := is.Attributes[key]; !ok || v != value { + if !ok { + return fmt.Errorf("%s: Attribute '%s' not found", name, key) + } + + return fmt.Errorf( + "%s: Attribute '%s' expected %#v, got %#v", + name, + key, + value, + v) } + return nil } // TestCheckNoResourceAttr is a TestCheckFunc which ensures that @@ -814,14 +950,31 @@ func TestCheckNoResourceAttr(name, key string) TestCheckFunc { return err } - if _, ok := is.Attributes[key]; ok { - return fmt.Errorf("%s: Attribute '%s' found when not expected", name, key) + return testCheckNoResourceAttr(is, name, key) + } +} + +// TestCheckModuleNoResourceAttr - as per TestCheckNoResourceAttr but with +// support for non-root modules +func TestCheckModuleNoResourceAttr(mp []string, name string, key string) TestCheckFunc { + return func(s *terraform.State) error { + is, err := modulePathPrimaryInstanceState(s, mp, name) + if err != nil { + return err } - return nil + return testCheckNoResourceAttr(is, name, key) } } +func testCheckNoResourceAttr(is *terraform.InstanceState, name string, key string) error { + if _, ok := is.Attributes[key]; ok { + return fmt.Errorf("%s: Attribute '%s' found when not expected", name, key) + } + + return nil +} + // TestMatchResourceAttr is a TestCheckFunc which checks that the value // in state for the given name/key combination matches the given regex. func TestMatchResourceAttr(name, key string, r *regexp.Regexp) TestCheckFunc { @@ -831,17 +984,34 @@ func TestMatchResourceAttr(name, key string, r *regexp.Regexp) TestCheckFunc { return err } - if !r.MatchString(is.Attributes[key]) { - return fmt.Errorf( - "%s: Attribute '%s' didn't match %q, got %#v", - name, - key, - r.String(), - is.Attributes[key]) + return testMatchResourceAttr(is, name, key, r) + } +} + +// TestModuleMatchResourceAttr - as per TestMatchResourceAttr but with +// support for non-root modules +func TestModuleMatchResourceAttr(mp []string, name string, key string, r *regexp.Regexp) TestCheckFunc { + return func(s *terraform.State) error { + is, err := modulePathPrimaryInstanceState(s, mp, name) + if err != nil { + return err } - return nil + return testMatchResourceAttr(is, name, key, r) + } +} + +func testMatchResourceAttr(is *terraform.InstanceState, name string, key string, r *regexp.Regexp) error { + if !r.MatchString(is.Attributes[key]) { + return fmt.Errorf( + "%s: Attribute '%s' didn't match %q, got %#v", + name, + key, + r.String(), + is.Attributes[key]) } + + return nil } // TestCheckResourceAttrPtr is like TestCheckResourceAttr except the @@ -853,6 +1023,14 @@ func TestCheckResourceAttrPtr(name string, key string, value *string) TestCheckF } } +// TestCheckModuleResourceAttrPtr - as per TestCheckResourceAttrPtr but with +// support for non-root modules +func TestCheckModuleResourceAttrPtr(mp []string, name string, key string, value *string) TestCheckFunc { + return func(s *terraform.State) error { + return TestCheckModuleResourceAttr(mp, name, key, *value)(s) + } +} + // TestCheckResourceAttrPair is a TestCheckFunc which validates that the values // in state for a pair of name/key combinations are equal. func TestCheckResourceAttrPair(nameFirst, keyFirst, nameSecond, keySecond string) TestCheckFunc { @@ -861,33 +1039,57 @@ func TestCheckResourceAttrPair(nameFirst, keyFirst, nameSecond, keySecond string if err != nil { return err } - vFirst, ok := isFirst.Attributes[keyFirst] - if !ok { - return fmt.Errorf("%s: Attribute '%s' not found", nameFirst, keyFirst) - } isSecond, err := primaryInstanceState(s, nameSecond) if err != nil { return err } - vSecond, ok := isSecond.Attributes[keySecond] - if !ok { - return fmt.Errorf("%s: Attribute '%s' not found", nameSecond, keySecond) + + return testCheckResourceAttrPair(isFirst, nameFirst, keyFirst, isSecond, nameSecond, keySecond) + } +} + +// TestCheckModuleResourceAttrPair - as per TestCheckResourceAttrPair but with +// support for non-root modules +func TestCheckModuleResourceAttrPair(mpFirst []string, nameFirst string, keyFirst string, mpSecond []string, nameSecond string, keySecond string) TestCheckFunc { + return func(s *terraform.State) error { + isFirst, err := modulePathPrimaryInstanceState(s, mpFirst, nameFirst) + if err != nil { + return err } - if vFirst != vSecond { - return fmt.Errorf( - "%s: Attribute '%s' expected %#v, got %#v", - nameFirst, - keyFirst, - vSecond, - vFirst) + isSecond, err := modulePathPrimaryInstanceState(s, mpSecond, nameSecond) + if err != nil { + return err } - return nil + return testCheckResourceAttrPair(isFirst, nameFirst, keyFirst, isSecond, nameSecond, keySecond) } } +func testCheckResourceAttrPair(isFirst *terraform.InstanceState, nameFirst string, keyFirst string, isSecond *terraform.InstanceState, nameSecond string, keySecond string) error { + vFirst, ok := isFirst.Attributes[keyFirst] + if !ok { + return fmt.Errorf("%s: Attribute '%s' not found", nameFirst, keyFirst) + } + + vSecond, ok := isSecond.Attributes[keySecond] + if !ok { + return fmt.Errorf("%s: Attribute '%s' not found", nameSecond, keySecond) + } + + if vFirst != vSecond { + return fmt.Errorf( + "%s: Attribute '%s' expected %#v, got %#v", + nameFirst, + keyFirst, + vSecond, + vFirst) + } + + return nil +} + // TestCheckOutput checks an output in the Terraform configuration func TestCheckOutput(name, value string) TestCheckFunc { return func(s *terraform.State) error { @@ -936,23 +1138,43 @@ type TestT interface { Error(args ...interface{}) Fatal(args ...interface{}) Skip(args ...interface{}) + Name() string + Parallel() } // This is set to true by unit tests to alter some behavior var testTesting = false -// primaryInstanceState returns the primary instance state for the given resource name. -func primaryInstanceState(s *terraform.State, name string) (*terraform.InstanceState, error) { - ms := s.RootModule() +// modulePrimaryInstanceState returns the instance state for the given resource +// name in a ModuleState +func modulePrimaryInstanceState(s *terraform.State, ms *terraform.ModuleState, name string) (*terraform.InstanceState, error) { rs, ok := ms.Resources[name] if !ok { - return nil, fmt.Errorf("Not found: %s", name) + return nil, fmt.Errorf("Not found: %s in %s", name, ms.Path) } is := rs.Primary if is == nil { - return nil, fmt.Errorf("No primary instance: %s", name) + return nil, fmt.Errorf("No primary instance: %s in %s", name, ms.Path) } return is, nil } + +// modulePathPrimaryInstanceState returns the primary instance state for the +// given resource name in a given module path. +func modulePathPrimaryInstanceState(s *terraform.State, mp []string, name string) (*terraform.InstanceState, error) { + ms := s.ModuleByPath(mp) + if ms == nil { + return nil, fmt.Errorf("No module found at: %s", mp) + } + + return modulePrimaryInstanceState(s, ms, name) +} + +// primaryInstanceState returns the primary instance state for the given +// resource name in the root module. +func primaryInstanceState(s *terraform.State, name string) (*terraform.InstanceState, error) { + ms := s.RootModule() + return modulePrimaryInstanceState(s, ms, name) +} -- cgit v1.2.3