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 }