github.com/hashicorp/terraform-plugin-sdk@v1.17.2/helper/resource/testing_new_import_state.go (about) 1 package resource 2 3 import ( 4 "fmt" 5 "reflect" 6 "strings" 7 "testing" 8 9 "github.com/davecgh/go-spew/spew" 10 "github.com/hashicorp/terraform-plugin-sdk/acctest" 11 "github.com/hashicorp/terraform-plugin-sdk/helper/schema" 12 "github.com/hashicorp/terraform-plugin-sdk/internal/addrs" 13 "github.com/hashicorp/terraform-plugin-sdk/terraform" 14 tftest "github.com/hashicorp/terraform-plugin-test/v2" 15 ) 16 17 func testStepNewImportState(t *testing.T, c TestCase, wd *tftest.WorkingDir, step TestStep, cfg string) error { 18 spewConf := spew.NewDefaultConfig() 19 spewConf.SortKeys = true 20 21 if step.ResourceName == "" { 22 t.Fatal("ResourceName is required for an import state test") 23 } 24 25 // get state from check sequence 26 var state *terraform.State 27 err := runProviderCommand(t, func() error { 28 state = getState(t, wd) 29 return nil 30 }, wd, c.ProviderFactories) 31 if err != nil { 32 return fmt.Errorf("Error getting state: %v", err) 33 } 34 35 // Determine the ID to import 36 var importId string 37 switch { 38 case step.ImportStateIdFunc != nil: 39 var err error 40 importId, err = step.ImportStateIdFunc(state) 41 if err != nil { 42 t.Fatal(err) 43 } 44 case step.ImportStateId != "": 45 importId = step.ImportStateId 46 default: 47 resource, err := testResource(step, state) 48 if err != nil { 49 t.Fatal(err) 50 } 51 importId = resource.Primary.ID 52 } 53 importId = step.ImportStateIdPrefix + importId 54 55 // Create working directory for import tests 56 if step.Config == "" { 57 step.Config = cfg 58 if step.Config == "" { 59 t.Fatal("Cannot import state with no specified config") 60 } 61 } 62 importWd := acctest.TestHelper.RequireNewWorkingDir(t) 63 defer importWd.Close() 64 importWd.RequireSetConfig(t, step.Config) 65 err = runProviderCommand(t, func() error { 66 return importWd.Init() 67 }, importWd, c.ProviderFactories) 68 if err != nil { 69 return fmt.Errorf("Error running init: %v", err) 70 } 71 72 err = runProviderCommand(t, func() error { 73 return importWd.Import(step.ResourceName, importId) 74 }, importWd, c.ProviderFactories) 75 if err != nil { 76 return err 77 } 78 79 var importState *terraform.State 80 err = runProviderCommand(t, func() error { 81 importState = getState(t, importWd) 82 return nil 83 }, importWd, c.ProviderFactories) 84 if err != nil { 85 return fmt.Errorf("Error getting state after import: %v", err) 86 } 87 88 // Go through the imported state and verify 89 if step.ImportStateCheck != nil { 90 var states []*terraform.InstanceState 91 for _, r := range importState.RootModule().Resources { 92 if r.Primary != nil { 93 is := r.Primary.DeepCopy() 94 is.Ephemeral.Type = r.Type // otherwise the check function cannot see the type 95 states = append(states, is) 96 } 97 } 98 if err := step.ImportStateCheck(states); err != nil { 99 t.Fatal(err) 100 } 101 } 102 103 // Verify that all the states match 104 if step.ImportStateVerify { 105 new := importState.RootModule().Resources 106 old := state.RootModule().Resources 107 108 for _, r := range new { 109 // Find the existing resource 110 var oldR *terraform.ResourceState 111 for r2Key, r2 := range old { 112 // Ensure that we do not match against data sources as they 113 // cannot be imported and are not what we want to verify. 114 // Mode is not present in ResourceState so we use the 115 // stringified ResourceStateKey for comparison. 116 if strings.HasPrefix(r2Key, "data.") { 117 continue 118 } 119 120 if r2.Primary != nil && r2.Primary.ID == r.Primary.ID && r2.Type == r.Type && r2.Provider == r.Provider { 121 oldR = r2 122 break 123 } 124 } 125 if oldR == nil { 126 t.Fatalf( 127 "Failed state verification, resource with ID %s not found", 128 r.Primary.ID) 129 } 130 131 // We'll try our best to find the schema for this resource type 132 // so we can ignore Removed fields during validation. If we fail 133 // to find the schema then we won't ignore them and so the test 134 // will need to rely on explicit ImportStateVerifyIgnore, though 135 // this shouldn't happen in any reasonable case. 136 // KEM CHANGE FROM OLD FRAMEWORK: Fail test if this happens. 137 var rsrcSchema *schema.Resource 138 139 // r.Provider at this point is `registry.terraform.io/hashicorp/blah` but we need `blah` 140 val, tfdiags := addrs.ParseProviderSourceString(r.Provider) 141 if tfdiags.HasErrors() { 142 t.Fatal(tfdiags.Err()) 143 } 144 providerName := val.Type 145 providerAddr, diags := addrs.ParseAbsProviderConfigStr("provider." + providerName + "." + r.Type) 146 if diags.HasErrors() { 147 t.Fatalf("Failed to find schema for resource with ID %s", r.Primary) 148 } 149 150 providerType := providerAddr.ProviderConfig.Type 151 if provider, ok := step.providers[providerType]; ok { 152 if provider, ok := provider.(*schema.Provider); ok { 153 rsrcSchema = provider.ResourcesMap[r.Type] 154 } 155 } 156 157 // don't add empty flatmapped containers, so we can more easily 158 // compare the attributes 159 skipEmpty := func(k, v string) bool { 160 if strings.HasSuffix(k, ".#") || strings.HasSuffix(k, ".%") { 161 if v == "0" { 162 return true 163 } 164 } 165 return false 166 } 167 168 // Compare their attributes 169 actual := make(map[string]string) 170 for k, v := range r.Primary.Attributes { 171 if skipEmpty(k, v) { 172 continue 173 } 174 actual[k] = v 175 } 176 177 expected := make(map[string]string) 178 for k, v := range oldR.Primary.Attributes { 179 if skipEmpty(k, v) { 180 continue 181 } 182 expected[k] = v 183 } 184 185 // Remove fields we're ignoring 186 for _, v := range step.ImportStateVerifyIgnore { 187 for k := range actual { 188 if strings.HasPrefix(k, v) { 189 delete(actual, k) 190 } 191 } 192 for k := range expected { 193 if strings.HasPrefix(k, v) { 194 delete(expected, k) 195 } 196 } 197 } 198 199 // Also remove any attributes that are marked as "Removed" in the 200 // schema, if we have a schema to check that against. 201 if rsrcSchema != nil { 202 for k := range actual { 203 for _, schema := range rsrcSchema.SchemasForFlatmapPath(k) { 204 if schema.Removed != "" { 205 delete(actual, k) 206 break 207 } 208 } 209 } 210 for k := range expected { 211 for _, schema := range rsrcSchema.SchemasForFlatmapPath(k) { 212 if schema.Removed != "" { 213 delete(expected, k) 214 break 215 } 216 } 217 } 218 } 219 220 // timeouts are only _sometimes_ added to state. To 221 // account for this, just don't compare timeouts at 222 // all. 223 for k := range actual { 224 if strings.HasPrefix(k, "timeouts.") { 225 delete(actual, k) 226 } 227 if k == "timeouts" { 228 delete(actual, k) 229 } 230 } 231 for k := range expected { 232 if strings.HasPrefix(k, "timeouts.") { 233 delete(expected, k) 234 } 235 if k == "timeouts" { 236 delete(expected, k) 237 } 238 } 239 240 if !reflect.DeepEqual(actual, expected) { 241 // Determine only the different attributes 242 for k, v := range expected { 243 if av, ok := actual[k]; ok && v == av { 244 delete(expected, k) 245 delete(actual, k) 246 } 247 } 248 249 t.Fatalf( 250 "ImportStateVerify attributes not equivalent. Difference is shown below. Top is actual, bottom is expected."+ 251 "\n\n%s\n\n%s", 252 spewConf.Sdump(actual), spewConf.Sdump(expected)) 253 } 254 } 255 } 256 257 return nil 258 }