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