github.com/hashicorp/terraform-plugin-sdk@v1.17.2/helper/resource/testing_new_config.go (about)

     1  package resource
     2  
     3  import (
     4  	"fmt"
     5  	"testing"
     6  
     7  	tfjson "github.com/hashicorp/terraform-json"
     8  	"github.com/hashicorp/terraform-plugin-sdk/terraform"
     9  	tftest "github.com/hashicorp/terraform-plugin-test/v2"
    10  )
    11  
    12  func testStepNewConfig(t *testing.T, c TestCase, wd *tftest.WorkingDir, step TestStep) error {
    13  	t.Helper()
    14  
    15  	var idRefreshCheck *terraform.ResourceState
    16  	idRefresh := c.IDRefreshName != ""
    17  
    18  	if !step.Destroy {
    19  		var state *terraform.State
    20  		err := runProviderCommand(t, func() error {
    21  			state = getState(t, wd)
    22  			return nil
    23  		}, wd, c.ProviderFactories)
    24  		if err != nil {
    25  			return fmt.Errorf("Error retrieving state: %v", err)
    26  		}
    27  		if err := testStepTaint(state, step); err != nil {
    28  			return fmt.Errorf("Error when tainting resources: %s", err)
    29  		}
    30  	}
    31  
    32  	err := wd.SetConfig(step.Config)
    33  	if err != nil {
    34  		return fmt.Errorf("Error setting config: %s", err)
    35  	}
    36  
    37  	// require a refresh before applying
    38  	// failing to do this will result in data sources not being updated
    39  	err = runProviderCommand(t, func() error {
    40  		return wd.Refresh()
    41  	}, wd, c.ProviderFactories)
    42  	if err != nil {
    43  		return fmt.Errorf("Error running pre-apply refresh: %v", err)
    44  	}
    45  
    46  	// If this step is a PlanOnly step, skip over this first Plan and
    47  	// subsequent Apply, and use the follow-up Plan that checks for
    48  	// permadiffs
    49  	if !step.PlanOnly {
    50  		// Plan!
    51  		err = runProviderCommand(t, func() error {
    52  			if step.Destroy {
    53  				return wd.CreateDestroyPlan()
    54  			}
    55  			return wd.CreatePlan()
    56  		}, wd, c.ProviderFactories)
    57  		if err != nil {
    58  			return fmt.Errorf("Error running pre-apply plan: %s", err)
    59  		}
    60  
    61  		// We need to keep a copy of the state prior to destroying such
    62  		// that the destroy steps can verify their behavior in the
    63  		// check function
    64  		var stateBeforeApplication *terraform.State
    65  		err = runProviderCommand(t, func() error {
    66  			stateBeforeApplication = getState(t, wd)
    67  			return nil
    68  		}, wd, c.ProviderFactories)
    69  		if err != nil {
    70  			return fmt.Errorf("Error retrieving pre-apply state: %s", err)
    71  		}
    72  
    73  		// Apply the diff, creating real resources
    74  		err = runProviderCommand(t, func() error {
    75  			return wd.Apply()
    76  		}, wd, c.ProviderFactories)
    77  		if err != nil {
    78  			if step.Destroy {
    79  				return fmt.Errorf("Error running destroy: %s", err)
    80  			}
    81  			return fmt.Errorf("Error running apply: %s", err)
    82  		}
    83  
    84  		var state *terraform.State
    85  		err = runProviderCommand(t, func() error {
    86  			state = getState(t, wd)
    87  			return nil
    88  		}, wd, c.ProviderFactories)
    89  		if err != nil {
    90  			return fmt.Errorf("error retrieving state after apply: %v", err)
    91  		}
    92  		if step.Check != nil {
    93  			state.IsBinaryDrivenTest = true
    94  			if step.Destroy {
    95  				if err := step.Check(stateBeforeApplication); err != nil {
    96  					return fmt.Errorf("Check failed: %s", err)
    97  				}
    98  			} else {
    99  				if err := step.Check(state); err != nil {
   100  					return fmt.Errorf("Check failed: %s", err)
   101  				}
   102  			}
   103  		}
   104  	}
   105  
   106  	// Test for perpetual diffs by performing a plan, a refresh, and another plan
   107  
   108  	// do a plan
   109  	err = runProviderCommand(t, func() error {
   110  		if step.Destroy {
   111  			return wd.CreateDestroyPlan()
   112  		}
   113  		return wd.CreatePlan()
   114  	}, wd, c.ProviderFactories)
   115  	if err != nil {
   116  		return fmt.Errorf("Error running post-apply plan: %s", err)
   117  	}
   118  
   119  	var plan *tfjson.Plan
   120  	err = runProviderCommand(t, func() error {
   121  		var err error
   122  		plan, err = wd.SavedPlan()
   123  		return err
   124  	}, wd, c.ProviderFactories)
   125  	if err != nil {
   126  		return fmt.Errorf("Error retrieving post-apply plan: %s", err)
   127  	}
   128  
   129  	if !planIsEmpty(plan) && !step.ExpectNonEmptyPlan {
   130  		var stdout string
   131  		err = runProviderCommand(t, func() error {
   132  			var err error
   133  			stdout, err = wd.SavedPlanStdout()
   134  			return err
   135  		}, wd, c.ProviderFactories)
   136  		if err != nil {
   137  			return fmt.Errorf("Error retrieving formatted plan output: %s", err)
   138  		}
   139  		return fmt.Errorf("After applying this test step, the plan was not empty.\nstdout:\n\n%s", stdout)
   140  	}
   141  
   142  	// do a refresh
   143  	if !step.Destroy || (step.Destroy && !step.PreventPostDestroyRefresh) {
   144  		err := runProviderCommand(t, func() error {
   145  			return wd.Refresh()
   146  		}, wd, c.ProviderFactories)
   147  		if err != nil {
   148  			return fmt.Errorf("Error running post-apply refresh: %s", err)
   149  		}
   150  	}
   151  
   152  	// do another plan
   153  	err = runProviderCommand(t, func() error {
   154  		if step.Destroy {
   155  			return wd.CreateDestroyPlan()
   156  		}
   157  		return wd.CreatePlan()
   158  	}, wd, c.ProviderFactories)
   159  	if err != nil {
   160  		return fmt.Errorf("Error running second post-apply plan: %s", err)
   161  	}
   162  
   163  	err = runProviderCommand(t, func() error {
   164  		var err error
   165  		plan, err = wd.SavedPlan()
   166  		return err
   167  	}, wd, c.ProviderFactories)
   168  	if err != nil {
   169  		return fmt.Errorf("Error retrieving second post-apply plan: %s", err)
   170  	}
   171  
   172  	// check if plan is empty
   173  	if !planIsEmpty(plan) && !step.ExpectNonEmptyPlan {
   174  		var stdout string
   175  		err = runProviderCommand(t, func() error {
   176  			var err error
   177  			stdout, err = wd.SavedPlanStdout()
   178  			return err
   179  		}, wd, c.ProviderFactories)
   180  		if err != nil {
   181  			return fmt.Errorf("Error retrieving formatted second plan output: %s", err)
   182  		}
   183  		return fmt.Errorf("After applying this test step and performing a `terraform refresh`, the plan was not empty.\nstdout\n\n%s", stdout)
   184  	} else if step.ExpectNonEmptyPlan && planIsEmpty(plan) {
   185  		return fmt.Errorf("Expected a non-empty plan, but got an empty plan!")
   186  	}
   187  
   188  	// ID-ONLY REFRESH
   189  	// If we've never checked an id-only refresh and our state isn't
   190  	// empty, find the first resource and test it.
   191  	var state *terraform.State
   192  	err = runProviderCommand(t, func() error {
   193  		state = getState(t, wd)
   194  		return nil
   195  	}, wd, c.ProviderFactories)
   196  	if err != nil {
   197  		return err
   198  	}
   199  	if idRefresh && idRefreshCheck == nil && !state.Empty() {
   200  		// Find the first non-nil resource in the state
   201  		for _, m := range state.Modules {
   202  			if len(m.Resources) > 0 {
   203  				if v, ok := m.Resources[c.IDRefreshName]; ok {
   204  					idRefreshCheck = v
   205  				}
   206  
   207  				break
   208  			}
   209  		}
   210  
   211  		// If we have an instance to check for refreshes, do it
   212  		// immediately. We do it in the middle of another test
   213  		// because it shouldn't affect the overall state (refresh
   214  		// is read-only semantically) and we want to fail early if
   215  		// this fails. If refresh isn't read-only, then this will have
   216  		// caught a different bug.
   217  		if idRefreshCheck != nil {
   218  			if err := testIDRefresh(c, t, wd, step, idRefreshCheck); err != nil {
   219  				return fmt.Errorf(
   220  					"[ERROR] Test: ID-only test failed: %s", err)
   221  			}
   222  		}
   223  	}
   224  
   225  	return nil
   226  }