github.com/jrperritt/terraform@v0.1.1-0.20170525065507-96f391dafc38/helper/resource/testing.go (about)

     1  package resource
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"io/ioutil"
     7  	"log"
     8  	"os"
     9  	"path/filepath"
    10  	"reflect"
    11  	"regexp"
    12  	"strings"
    13  	"testing"
    14  
    15  	"github.com/davecgh/go-spew/spew"
    16  	"github.com/hashicorp/go-getter"
    17  	"github.com/hashicorp/go-multierror"
    18  	"github.com/hashicorp/terraform/config/module"
    19  	"github.com/hashicorp/terraform/helper/logging"
    20  	"github.com/hashicorp/terraform/terraform"
    21  )
    22  
    23  const TestEnvVar = "TF_ACC"
    24  
    25  // TestProvider can be implemented by any ResourceProvider to provide custom
    26  // reset functionality at the start of an acceptance test.
    27  // The helper/schema Provider implements this interface.
    28  type TestProvider interface {
    29  	TestReset() error
    30  }
    31  
    32  // TestCheckFunc is the callback type used with acceptance tests to check
    33  // the state of a resource. The state passed in is the latest state known,
    34  // or in the case of being after a destroy, it is the last known state when
    35  // it was created.
    36  type TestCheckFunc func(*terraform.State) error
    37  
    38  // ImportStateCheckFunc is the check function for ImportState tests
    39  type ImportStateCheckFunc func([]*terraform.InstanceState) error
    40  
    41  // TestCase is a single acceptance test case used to test the apply/destroy
    42  // lifecycle of a resource in a specific configuration.
    43  //
    44  // When the destroy plan is executed, the config from the last TestStep
    45  // is used to plan it.
    46  type TestCase struct {
    47  	// IsUnitTest allows a test to run regardless of the TF_ACC
    48  	// environment variable. This should be used with care - only for
    49  	// fast tests on local resources (e.g. remote state with a local
    50  	// backend) but can be used to increase confidence in correct
    51  	// operation of Terraform without waiting for a full acctest run.
    52  	IsUnitTest bool
    53  
    54  	// PreCheck, if non-nil, will be called before any test steps are
    55  	// executed. It will only be executed in the case that the steps
    56  	// would run, so it can be used for some validation before running
    57  	// acceptance tests, such as verifying that keys are setup.
    58  	PreCheck func()
    59  
    60  	// Providers is the ResourceProvider that will be under test.
    61  	//
    62  	// Alternately, ProviderFactories can be specified for the providers
    63  	// that are valid. This takes priority over Providers.
    64  	//
    65  	// The end effect of each is the same: specifying the providers that
    66  	// are used within the tests.
    67  	Providers         map[string]terraform.ResourceProvider
    68  	ProviderFactories map[string]terraform.ResourceProviderFactory
    69  
    70  	// PreventPostDestroyRefresh can be set to true for cases where data sources
    71  	// are tested alongside real resources
    72  	PreventPostDestroyRefresh bool
    73  
    74  	// CheckDestroy is called after the resource is finally destroyed
    75  	// to allow the tester to test that the resource is truly gone.
    76  	CheckDestroy TestCheckFunc
    77  
    78  	// Steps are the apply sequences done within the context of the
    79  	// same state. Each step can have its own check to verify correctness.
    80  	Steps []TestStep
    81  
    82  	// The settings below control the "ID-only refresh test." This is
    83  	// an enabled-by-default test that tests that a refresh can be
    84  	// refreshed with only an ID to result in the same attributes.
    85  	// This validates completeness of Refresh.
    86  	//
    87  	// IDRefreshName is the name of the resource to check. This will
    88  	// default to the first non-nil primary resource in the state.
    89  	//
    90  	// IDRefreshIgnore is a list of configuration keys that will be ignored.
    91  	IDRefreshName   string
    92  	IDRefreshIgnore []string
    93  }
    94  
    95  // TestStep is a single apply sequence of a test, done within the
    96  // context of a state.
    97  //
    98  // Multiple TestSteps can be sequenced in a Test to allow testing
    99  // potentially complex update logic. In general, simply create/destroy
   100  // tests will only need one step.
   101  type TestStep struct {
   102  	// ResourceName should be set to the name of the resource
   103  	// that is being tested. Example: "aws_instance.foo". Various test
   104  	// modes use this to auto-detect state information.
   105  	//
   106  	// This is only required if the test mode settings below say it is
   107  	// for the mode you're using.
   108  	ResourceName string
   109  
   110  	// PreConfig is called before the Config is applied to perform any per-step
   111  	// setup that needs to happen. This is called regardless of "test mode"
   112  	// below.
   113  	PreConfig func()
   114  
   115  	//---------------------------------------------------------------
   116  	// Test modes. One of the following groups of settings must be
   117  	// set to determine what the test step will do. Ideally we would've
   118  	// used Go interfaces here but there are now hundreds of tests we don't
   119  	// want to re-type so instead we just determine which step logic
   120  	// to run based on what settings below are set.
   121  	//---------------------------------------------------------------
   122  
   123  	//---------------------------------------------------------------
   124  	// Plan, Apply testing
   125  	//---------------------------------------------------------------
   126  
   127  	// Config a string of the configuration to give to Terraform. If this
   128  	// is set, then the TestCase will execute this step with the same logic
   129  	// as a `terraform apply`.
   130  	Config string
   131  
   132  	// Check is called after the Config is applied. Use this step to
   133  	// make your own API calls to check the status of things, and to
   134  	// inspect the format of the ResourceState itself.
   135  	//
   136  	// If an error is returned, the test will fail. In this case, a
   137  	// destroy plan will still be attempted.
   138  	//
   139  	// If this is nil, no check is done on this step.
   140  	Check TestCheckFunc
   141  
   142  	// Destroy will create a destroy plan if set to true.
   143  	Destroy bool
   144  
   145  	// ExpectNonEmptyPlan can be set to true for specific types of tests that are
   146  	// looking to verify that a diff occurs
   147  	ExpectNonEmptyPlan bool
   148  
   149  	// ExpectError allows the construction of test cases that we expect to fail
   150  	// with an error. The specified regexp must match against the error for the
   151  	// test to pass.
   152  	ExpectError *regexp.Regexp
   153  
   154  	// PlanOnly can be set to only run `plan` with this configuration, and not
   155  	// actually apply it. This is useful for ensuring config changes result in
   156  	// no-op plans
   157  	PlanOnly bool
   158  
   159  	// PreventPostDestroyRefresh can be set to true for cases where data sources
   160  	// are tested alongside real resources
   161  	PreventPostDestroyRefresh bool
   162  
   163  	//---------------------------------------------------------------
   164  	// ImportState testing
   165  	//---------------------------------------------------------------
   166  
   167  	// ImportState, if true, will test the functionality of ImportState
   168  	// by importing the resource with ResourceName (must be set) and the
   169  	// ID of that resource.
   170  	ImportState bool
   171  
   172  	// ImportStateId is the ID to perform an ImportState operation with.
   173  	// This is optional. If it isn't set, then the resource ID is automatically
   174  	// determined by inspecting the state for ResourceName's ID.
   175  	ImportStateId string
   176  
   177  	// ImportStateIdPrefix is the prefix added in front of ImportStateId.
   178  	// This can be useful in complex import cases, where more than one
   179  	// attribute needs to be passed on as the Import ID. Mainly in cases
   180  	// where the ID is not known, and a known prefix needs to be added to
   181  	// the unset ImportStateId field.
   182  	ImportStateIdPrefix string
   183  
   184  	// ImportStateCheck checks the results of ImportState. It should be
   185  	// used to verify that the resulting value of ImportState has the
   186  	// proper resources, IDs, and attributes.
   187  	ImportStateCheck ImportStateCheckFunc
   188  
   189  	// ImportStateVerify, if true, will also check that the state values
   190  	// that are finally put into the state after import match for all the
   191  	// IDs returned by the Import.
   192  	//
   193  	// ImportStateVerifyIgnore are fields that should not be verified to
   194  	// be equal. These can be set to ephemeral fields or fields that can't
   195  	// be refreshed and don't matter.
   196  	ImportStateVerify       bool
   197  	ImportStateVerifyIgnore []string
   198  }
   199  
   200  // Test performs an acceptance test on a resource.
   201  //
   202  // Tests are not run unless an environmental variable "TF_ACC" is
   203  // set to some non-empty value. This is to avoid test cases surprising
   204  // a user by creating real resources.
   205  //
   206  // Tests will fail unless the verbose flag (`go test -v`, or explicitly
   207  // the "-test.v" flag) is set. Because some acceptance tests take quite
   208  // long, we require the verbose flag so users are able to see progress
   209  // output.
   210  func Test(t TestT, c TestCase) {
   211  	// We only run acceptance tests if an env var is set because they're
   212  	// slow and generally require some outside configuration. You can opt out
   213  	// of this with OverrideEnvVar on individual TestCases.
   214  	if os.Getenv(TestEnvVar) == "" && !c.IsUnitTest {
   215  		t.Skip(fmt.Sprintf(
   216  			"Acceptance tests skipped unless env '%s' set",
   217  			TestEnvVar))
   218  		return
   219  	}
   220  
   221  	logWriter, err := logging.LogOutput()
   222  	if err != nil {
   223  		t.Error(fmt.Errorf("error setting up logging: %s", err))
   224  	}
   225  	log.SetOutput(logWriter)
   226  
   227  	// We require verbose mode so that the user knows what is going on.
   228  	if !testTesting && !testing.Verbose() && !c.IsUnitTest {
   229  		t.Fatal("Acceptance tests must be run with the -v flag on tests")
   230  		return
   231  	}
   232  
   233  	// Run the PreCheck if we have it
   234  	if c.PreCheck != nil {
   235  		c.PreCheck()
   236  	}
   237  
   238  	ctxProviders, err := testProviderFactories(c)
   239  	if err != nil {
   240  		t.Fatal(err)
   241  	}
   242  	opts := terraform.ContextOpts{Providers: ctxProviders}
   243  
   244  	// A single state variable to track the lifecycle, starting with no state
   245  	var state *terraform.State
   246  
   247  	// Go through each step and run it
   248  	var idRefreshCheck *terraform.ResourceState
   249  	idRefresh := c.IDRefreshName != ""
   250  	errored := false
   251  	for i, step := range c.Steps {
   252  		var err error
   253  		log.Printf("[WARN] Test: Executing step %d", i)
   254  
   255  		// Determine the test mode to execute
   256  		if step.Config != "" {
   257  			state, err = testStepConfig(opts, state, step)
   258  		} else if step.ImportState {
   259  			state, err = testStepImportState(opts, state, step)
   260  		} else {
   261  			err = fmt.Errorf(
   262  				"unknown test mode for step. Please see TestStep docs\n\n%#v",
   263  				step)
   264  		}
   265  
   266  		// If there was an error, exit
   267  		if err != nil {
   268  			// Perhaps we expected an error? Check if it matches
   269  			if step.ExpectError != nil {
   270  				if !step.ExpectError.MatchString(err.Error()) {
   271  					errored = true
   272  					t.Error(fmt.Sprintf(
   273  						"Step %d, expected error:\n\n%s\n\nTo match:\n\n%s\n\n",
   274  						i, err, step.ExpectError))
   275  					break
   276  				}
   277  			} else {
   278  				errored = true
   279  				t.Error(fmt.Sprintf(
   280  					"Step %d error: %s", i, err))
   281  				break
   282  			}
   283  		}
   284  
   285  		// If we've never checked an id-only refresh and our state isn't
   286  		// empty, find the first resource and test it.
   287  		if idRefresh && idRefreshCheck == nil && !state.Empty() {
   288  			// Find the first non-nil resource in the state
   289  			for _, m := range state.Modules {
   290  				if len(m.Resources) > 0 {
   291  					if v, ok := m.Resources[c.IDRefreshName]; ok {
   292  						idRefreshCheck = v
   293  					}
   294  
   295  					break
   296  				}
   297  			}
   298  
   299  			// If we have an instance to check for refreshes, do it
   300  			// immediately. We do it in the middle of another test
   301  			// because it shouldn't affect the overall state (refresh
   302  			// is read-only semantically) and we want to fail early if
   303  			// this fails. If refresh isn't read-only, then this will have
   304  			// caught a different bug.
   305  			if idRefreshCheck != nil {
   306  				log.Printf(
   307  					"[WARN] Test: Running ID-only refresh check on %s",
   308  					idRefreshCheck.Primary.ID)
   309  				if err := testIDOnlyRefresh(c, opts, step, idRefreshCheck); err != nil {
   310  					log.Printf("[ERROR] Test: ID-only test failed: %s", err)
   311  					t.Error(fmt.Sprintf(
   312  						"[ERROR] Test: ID-only test failed: %s", err))
   313  					break
   314  				}
   315  			}
   316  		}
   317  	}
   318  
   319  	// If we never checked an id-only refresh, it is a failure.
   320  	if idRefresh {
   321  		if !errored && len(c.Steps) > 0 && idRefreshCheck == nil {
   322  			t.Error("ID-only refresh check never ran.")
   323  		}
   324  	}
   325  
   326  	// If we have a state, then run the destroy
   327  	if state != nil {
   328  		lastStep := c.Steps[len(c.Steps)-1]
   329  		destroyStep := TestStep{
   330  			Config:                    lastStep.Config,
   331  			Check:                     c.CheckDestroy,
   332  			Destroy:                   true,
   333  			PreventPostDestroyRefresh: c.PreventPostDestroyRefresh,
   334  		}
   335  
   336  		log.Printf("[WARN] Test: Executing destroy step")
   337  		state, err := testStep(opts, state, destroyStep)
   338  		if err != nil {
   339  			t.Error(fmt.Sprintf(
   340  				"Error destroying resource! WARNING: Dangling resources\n"+
   341  					"may exist. The full state and error is shown below.\n\n"+
   342  					"Error: %s\n\nState: %s",
   343  				err,
   344  				state))
   345  		}
   346  	} else {
   347  		log.Printf("[WARN] Skipping destroy test since there is no state.")
   348  	}
   349  }
   350  
   351  // testProviderFactories is a helper to build the ResourceProviderFactory map
   352  // with pre instantiated ResourceProviders, so that we can reset them for the
   353  // test, while only calling the factory function once.
   354  // Any errors are stored so that they can be returned by the factory in
   355  // terraform to match non-test behavior.
   356  func testProviderFactories(c TestCase) (map[string]terraform.ResourceProviderFactory, error) {
   357  	ctxProviders := c.ProviderFactories // make(map[string]terraform.ResourceProviderFactory)
   358  	if ctxProviders == nil {
   359  		ctxProviders = make(map[string]terraform.ResourceProviderFactory)
   360  	}
   361  	// add any fixed providers
   362  	for k, p := range c.Providers {
   363  		ctxProviders[k] = terraform.ResourceProviderFactoryFixed(p)
   364  	}
   365  
   366  	// reset the providers if needed
   367  	for k, pf := range ctxProviders {
   368  		// we can ignore any errors here, if we don't have a provider to reset
   369  		// the error will be handled later
   370  		p, err := pf()
   371  		if err != nil {
   372  			return nil, err
   373  		}
   374  		if p, ok := p.(TestProvider); ok {
   375  			err := p.TestReset()
   376  			if err != nil {
   377  				return nil, fmt.Errorf("[ERROR] failed to reset provider %q: %s", k, err)
   378  			}
   379  		}
   380  	}
   381  
   382  	return ctxProviders, nil
   383  }
   384  
   385  // UnitTest is a helper to force the acceptance testing harness to run in the
   386  // normal unit test suite. This should only be used for resource that don't
   387  // have any external dependencies.
   388  func UnitTest(t TestT, c TestCase) {
   389  	c.IsUnitTest = true
   390  	Test(t, c)
   391  }
   392  
   393  func testIDOnlyRefresh(c TestCase, opts terraform.ContextOpts, step TestStep, r *terraform.ResourceState) error {
   394  	// TODO: We guard by this right now so master doesn't explode. We
   395  	// need to remove this eventually to make this part of the normal tests.
   396  	if os.Getenv("TF_ACC_IDONLY") == "" {
   397  		return nil
   398  	}
   399  
   400  	name := fmt.Sprintf("%s.foo", r.Type)
   401  
   402  	// Build the state. The state is just the resource with an ID. There
   403  	// are no attributes. We only set what is needed to perform a refresh.
   404  	state := terraform.NewState()
   405  	state.RootModule().Resources[name] = &terraform.ResourceState{
   406  		Type: r.Type,
   407  		Primary: &terraform.InstanceState{
   408  			ID: r.Primary.ID,
   409  		},
   410  	}
   411  
   412  	// Create the config module. We use the full config because Refresh
   413  	// doesn't have access to it and we may need things like provider
   414  	// configurations. The initial implementation of id-only checks used
   415  	// an empty config module, but that caused the aforementioned problems.
   416  	mod, err := testModule(opts, step)
   417  	if err != nil {
   418  		return err
   419  	}
   420  
   421  	// Initialize the context
   422  	opts.Module = mod
   423  	opts.State = state
   424  	ctx, err := terraform.NewContext(&opts)
   425  	if err != nil {
   426  		return err
   427  	}
   428  	if ws, es := ctx.Validate(); len(ws) > 0 || len(es) > 0 {
   429  		if len(es) > 0 {
   430  			estrs := make([]string, len(es))
   431  			for i, e := range es {
   432  				estrs[i] = e.Error()
   433  			}
   434  			return fmt.Errorf(
   435  				"Configuration is invalid.\n\nWarnings: %#v\n\nErrors: %#v",
   436  				ws, estrs)
   437  		}
   438  
   439  		log.Printf("[WARN] Config warnings: %#v", ws)
   440  	}
   441  
   442  	// Refresh!
   443  	state, err = ctx.Refresh()
   444  	if err != nil {
   445  		return fmt.Errorf("Error refreshing: %s", err)
   446  	}
   447  
   448  	// Verify attribute equivalence.
   449  	actualR := state.RootModule().Resources[name]
   450  	if actualR == nil {
   451  		return fmt.Errorf("Resource gone!")
   452  	}
   453  	if actualR.Primary == nil {
   454  		return fmt.Errorf("Resource has no primary instance")
   455  	}
   456  	actual := actualR.Primary.Attributes
   457  	expected := r.Primary.Attributes
   458  	// Remove fields we're ignoring
   459  	for _, v := range c.IDRefreshIgnore {
   460  		for k, _ := range actual {
   461  			if strings.HasPrefix(k, v) {
   462  				delete(actual, k)
   463  			}
   464  		}
   465  		for k, _ := range expected {
   466  			if strings.HasPrefix(k, v) {
   467  				delete(expected, k)
   468  			}
   469  		}
   470  	}
   471  
   472  	if !reflect.DeepEqual(actual, expected) {
   473  		// Determine only the different attributes
   474  		for k, v := range expected {
   475  			if av, ok := actual[k]; ok && v == av {
   476  				delete(expected, k)
   477  				delete(actual, k)
   478  			}
   479  		}
   480  
   481  		spewConf := spew.NewDefaultConfig()
   482  		spewConf.SortKeys = true
   483  		return fmt.Errorf(
   484  			"Attributes not equivalent. Difference is shown below. Top is actual, bottom is expected."+
   485  				"\n\n%s\n\n%s",
   486  			spewConf.Sdump(actual), spewConf.Sdump(expected))
   487  	}
   488  
   489  	return nil
   490  }
   491  
   492  func testModule(
   493  	opts terraform.ContextOpts,
   494  	step TestStep) (*module.Tree, error) {
   495  	if step.PreConfig != nil {
   496  		step.PreConfig()
   497  	}
   498  
   499  	cfgPath, err := ioutil.TempDir("", "tf-test")
   500  	if err != nil {
   501  		return nil, fmt.Errorf(
   502  			"Error creating temporary directory for config: %s", err)
   503  	}
   504  	defer os.RemoveAll(cfgPath)
   505  
   506  	// Write the configuration
   507  	cfgF, err := os.Create(filepath.Join(cfgPath, "main.tf"))
   508  	if err != nil {
   509  		return nil, fmt.Errorf(
   510  			"Error creating temporary file for config: %s", err)
   511  	}
   512  
   513  	_, err = io.Copy(cfgF, strings.NewReader(step.Config))
   514  	cfgF.Close()
   515  	if err != nil {
   516  		return nil, fmt.Errorf(
   517  			"Error creating temporary file for config: %s", err)
   518  	}
   519  
   520  	// Parse the configuration
   521  	mod, err := module.NewTreeModule("", cfgPath)
   522  	if err != nil {
   523  		return nil, fmt.Errorf(
   524  			"Error loading configuration: %s", err)
   525  	}
   526  
   527  	// Load the modules
   528  	modStorage := &getter.FolderStorage{
   529  		StorageDir: filepath.Join(cfgPath, ".tfmodules"),
   530  	}
   531  	err = mod.Load(modStorage, module.GetModeGet)
   532  	if err != nil {
   533  		return nil, fmt.Errorf("Error downloading modules: %s", err)
   534  	}
   535  
   536  	return mod, nil
   537  }
   538  
   539  func testResource(c TestStep, state *terraform.State) (*terraform.ResourceState, error) {
   540  	if c.ResourceName == "" {
   541  		return nil, fmt.Errorf("ResourceName must be set in TestStep")
   542  	}
   543  
   544  	for _, m := range state.Modules {
   545  		if len(m.Resources) > 0 {
   546  			if v, ok := m.Resources[c.ResourceName]; ok {
   547  				return v, nil
   548  			}
   549  		}
   550  	}
   551  
   552  	return nil, fmt.Errorf(
   553  		"Resource specified by ResourceName couldn't be found: %s", c.ResourceName)
   554  }
   555  
   556  // ComposeTestCheckFunc lets you compose multiple TestCheckFuncs into
   557  // a single TestCheckFunc.
   558  //
   559  // As a user testing their provider, this lets you decompose your checks
   560  // into smaller pieces more easily.
   561  func ComposeTestCheckFunc(fs ...TestCheckFunc) TestCheckFunc {
   562  	return func(s *terraform.State) error {
   563  		for i, f := range fs {
   564  			if err := f(s); err != nil {
   565  				return fmt.Errorf("Check %d/%d error: %s", i+1, len(fs), err)
   566  			}
   567  		}
   568  
   569  		return nil
   570  	}
   571  }
   572  
   573  // ComposeAggregateTestCheckFunc lets you compose multiple TestCheckFuncs into
   574  // a single TestCheckFunc.
   575  //
   576  // As a user testing their provider, this lets you decompose your checks
   577  // into smaller pieces more easily.
   578  //
   579  // Unlike ComposeTestCheckFunc, ComposeAggergateTestCheckFunc runs _all_ of the
   580  // TestCheckFuncs and aggregates failures.
   581  func ComposeAggregateTestCheckFunc(fs ...TestCheckFunc) TestCheckFunc {
   582  	return func(s *terraform.State) error {
   583  		var result *multierror.Error
   584  
   585  		for i, f := range fs {
   586  			if err := f(s); err != nil {
   587  				result = multierror.Append(result, fmt.Errorf("Check %d/%d error: %s", i+1, len(fs), err))
   588  			}
   589  		}
   590  
   591  		return result.ErrorOrNil()
   592  	}
   593  }
   594  
   595  // TestCheckResourceAttrSet is a TestCheckFunc which ensures a value
   596  // exists in state for the given name/key combination. It is useful when
   597  // testing that computed values were set, when it is not possible to
   598  // know ahead of time what the values will be.
   599  func TestCheckResourceAttrSet(name, key string) TestCheckFunc {
   600  	return func(s *terraform.State) error {
   601  		is, err := primaryInstanceState(s, name)
   602  		if err != nil {
   603  			return err
   604  		}
   605  
   606  		if val, ok := is.Attributes[key]; ok && val != "" {
   607  			return nil
   608  		}
   609  
   610  		return fmt.Errorf("%s: Attribute '%s' expected to be set", name, key)
   611  	}
   612  }
   613  
   614  // TestCheckResourceAttr is a TestCheckFunc which validates
   615  // the value in state for the given name/key combination.
   616  func TestCheckResourceAttr(name, key, value string) TestCheckFunc {
   617  	return func(s *terraform.State) error {
   618  		is, err := primaryInstanceState(s, name)
   619  		if err != nil {
   620  			return err
   621  		}
   622  
   623  		if v, ok := is.Attributes[key]; !ok || v != value {
   624  			if !ok {
   625  				return fmt.Errorf("%s: Attribute '%s' not found", name, key)
   626  			}
   627  
   628  			return fmt.Errorf(
   629  				"%s: Attribute '%s' expected %#v, got %#v",
   630  				name,
   631  				key,
   632  				value,
   633  				v)
   634  		}
   635  
   636  		return nil
   637  	}
   638  }
   639  
   640  // TestCheckNoResourceAttr is a TestCheckFunc which ensures that
   641  // NO value exists in state for the given name/key combination.
   642  func TestCheckNoResourceAttr(name, key string) TestCheckFunc {
   643  	return func(s *terraform.State) error {
   644  		is, err := primaryInstanceState(s, name)
   645  		if err != nil {
   646  			return err
   647  		}
   648  
   649  		if _, ok := is.Attributes[key]; ok {
   650  			return fmt.Errorf("%s: Attribute '%s' found when not expected", name, key)
   651  		}
   652  
   653  		return nil
   654  	}
   655  }
   656  
   657  // TestMatchResourceAttr is a TestCheckFunc which checks that the value
   658  // in state for the given name/key combination matches the given regex.
   659  func TestMatchResourceAttr(name, key string, r *regexp.Regexp) TestCheckFunc {
   660  	return func(s *terraform.State) error {
   661  		is, err := primaryInstanceState(s, name)
   662  		if err != nil {
   663  			return err
   664  		}
   665  
   666  		if !r.MatchString(is.Attributes[key]) {
   667  			return fmt.Errorf(
   668  				"%s: Attribute '%s' didn't match %q, got %#v",
   669  				name,
   670  				key,
   671  				r.String(),
   672  				is.Attributes[key])
   673  		}
   674  
   675  		return nil
   676  	}
   677  }
   678  
   679  // TestCheckResourceAttrPtr is like TestCheckResourceAttr except the
   680  // value is a pointer so that it can be updated while the test is running.
   681  // It will only be dereferenced at the point this step is run.
   682  func TestCheckResourceAttrPtr(name string, key string, value *string) TestCheckFunc {
   683  	return func(s *terraform.State) error {
   684  		return TestCheckResourceAttr(name, key, *value)(s)
   685  	}
   686  }
   687  
   688  // TestCheckResourceAttrPair is a TestCheckFunc which validates that the values
   689  // in state for a pair of name/key combinations are equal.
   690  func TestCheckResourceAttrPair(nameFirst, keyFirst, nameSecond, keySecond string) TestCheckFunc {
   691  	return func(s *terraform.State) error {
   692  		isFirst, err := primaryInstanceState(s, nameFirst)
   693  		if err != nil {
   694  			return err
   695  		}
   696  		vFirst, ok := isFirst.Attributes[keyFirst]
   697  		if !ok {
   698  			return fmt.Errorf("%s: Attribute '%s' not found", nameFirst, keyFirst)
   699  		}
   700  
   701  		isSecond, err := primaryInstanceState(s, nameSecond)
   702  		if err != nil {
   703  			return err
   704  		}
   705  		vSecond, ok := isSecond.Attributes[keySecond]
   706  		if !ok {
   707  			return fmt.Errorf("%s: Attribute '%s' not found", nameSecond, keySecond)
   708  		}
   709  
   710  		if vFirst != vSecond {
   711  			return fmt.Errorf(
   712  				"%s: Attribute '%s' expected %#v, got %#v",
   713  				nameFirst,
   714  				keyFirst,
   715  				vSecond,
   716  				vFirst)
   717  		}
   718  
   719  		return nil
   720  	}
   721  }
   722  
   723  // TestCheckOutput checks an output in the Terraform configuration
   724  func TestCheckOutput(name, value string) TestCheckFunc {
   725  	return func(s *terraform.State) error {
   726  		ms := s.RootModule()
   727  		rs, ok := ms.Outputs[name]
   728  		if !ok {
   729  			return fmt.Errorf("Not found: %s", name)
   730  		}
   731  
   732  		if rs.Value != value {
   733  			return fmt.Errorf(
   734  				"Output '%s': expected %#v, got %#v",
   735  				name,
   736  				value,
   737  				rs)
   738  		}
   739  
   740  		return nil
   741  	}
   742  }
   743  
   744  func TestMatchOutput(name string, r *regexp.Regexp) TestCheckFunc {
   745  	return func(s *terraform.State) error {
   746  		ms := s.RootModule()
   747  		rs, ok := ms.Outputs[name]
   748  		if !ok {
   749  			return fmt.Errorf("Not found: %s", name)
   750  		}
   751  
   752  		if !r.MatchString(rs.Value.(string)) {
   753  			return fmt.Errorf(
   754  				"Output '%s': %#v didn't match %q",
   755  				name,
   756  				rs,
   757  				r.String())
   758  		}
   759  
   760  		return nil
   761  	}
   762  }
   763  
   764  // TestT is the interface used to handle the test lifecycle of a test.
   765  //
   766  // Users should just use a *testing.T object, which implements this.
   767  type TestT interface {
   768  	Error(args ...interface{})
   769  	Fatal(args ...interface{})
   770  	Skip(args ...interface{})
   771  }
   772  
   773  // This is set to true by unit tests to alter some behavior
   774  var testTesting = false
   775  
   776  // primaryInstanceState returns the primary instance state for the given resource name.
   777  func primaryInstanceState(s *terraform.State, name string) (*terraform.InstanceState, error) {
   778  	ms := s.RootModule()
   779  	rs, ok := ms.Resources[name]
   780  	if !ok {
   781  		return nil, fmt.Errorf("Not found: %s", name)
   782  	}
   783  
   784  	is := rs.Primary
   785  	if is == nil {
   786  		return nil, fmt.Errorf("No primary instance: %s", name)
   787  	}
   788  
   789  	return is, nil
   790  }