github.com/hashicorp/terraform-plugin-sdk@v1.17.2/helper/resource/testing_import_state.go (about) 1 package resource 2 3 import ( 4 "fmt" 5 "log" 6 "reflect" 7 "strings" 8 9 "github.com/davecgh/go-spew/spew" 10 "github.com/hashicorp/hcl/v2" 11 "github.com/hashicorp/hcl/v2/hclsyntax" 12 13 "github.com/hashicorp/terraform-plugin-sdk/helper/schema" 14 "github.com/hashicorp/terraform-plugin-sdk/internal/addrs" 15 "github.com/hashicorp/terraform-plugin-sdk/internal/states" 16 "github.com/hashicorp/terraform-plugin-sdk/terraform" 17 ) 18 19 // testStepImportState runs an imort state test step 20 func testStepImportState( 21 opts terraform.ContextOpts, 22 state *terraform.State, 23 step TestStep) (*terraform.State, error) { 24 25 // Determine the ID to import 26 var importId string 27 switch { 28 case step.ImportStateIdFunc != nil: 29 var err error 30 importId, err = step.ImportStateIdFunc(state) 31 if err != nil { 32 return state, err 33 } 34 case step.ImportStateId != "": 35 importId = step.ImportStateId 36 default: 37 resource, err := testResource(step, state) 38 if err != nil { 39 return state, err 40 } 41 importId = resource.Primary.ID 42 } 43 44 importPrefix := step.ImportStateIdPrefix 45 if importPrefix != "" { 46 importId = fmt.Sprintf("%s%s", importPrefix, importId) 47 } 48 49 // Setup the context. We initialize with an empty state. We use the 50 // full config for provider configurations. 51 cfg, err := testConfig(opts, step) 52 if err != nil { 53 return state, err 54 } 55 56 opts.Config = cfg 57 58 // import tests start with empty state 59 opts.State = states.NewState() 60 61 ctx, stepDiags := terraform.NewContext(&opts) 62 if stepDiags.HasErrors() { 63 return state, stepDiags.Err() 64 } 65 66 // The test step provides the resource address as a string, so we need 67 // to parse it to get an addrs.AbsResourceAddress to pass in to the 68 // import method. 69 traversal, hclDiags := hclsyntax.ParseTraversalAbs([]byte(step.ResourceName), "", hcl.Pos{}) 70 if hclDiags.HasErrors() { 71 return nil, hclDiags 72 } 73 importAddr, stepDiags := addrs.ParseAbsResourceInstance(traversal) 74 if stepDiags.HasErrors() { 75 return nil, stepDiags.Err() 76 } 77 78 // Do the import 79 importedState, stepDiags := ctx.Import(&terraform.ImportOpts{ 80 // Set the module so that any provider config is loaded 81 Config: cfg, 82 83 Targets: []*terraform.ImportTarget{ 84 { 85 Addr: importAddr, 86 ID: importId, 87 }, 88 }, 89 }) 90 if stepDiags.HasErrors() { 91 log.Printf("[ERROR] Test: ImportState failure: %s", stepDiags.Err()) 92 return state, stepDiags.Err() 93 } 94 95 newState, err := shimNewState(importedState, step.providers) 96 if err != nil { 97 return nil, err 98 } 99 100 // Go through the new state and verify 101 if step.ImportStateCheck != nil { 102 var states []*terraform.InstanceState 103 for _, r := range newState.RootModule().Resources { 104 if r.Primary != nil { 105 is := r.Primary.DeepCopy() 106 is.Ephemeral.Type = r.Type // otherwise the check function cannot see the type 107 states = append(states, is) 108 } 109 } 110 if err := step.ImportStateCheck(states); err != nil { 111 return state, err 112 } 113 } 114 115 // Verify that all the states match 116 if step.ImportStateVerify { 117 new := newState.RootModule().Resources 118 old := state.RootModule().Resources 119 for _, r := range new { 120 // Find the existing resource 121 var oldR *terraform.ResourceState 122 for _, r2 := range old { 123 if r2.Primary != nil && r2.Primary.ID == r.Primary.ID && r2.Type == r.Type { 124 oldR = r2 125 break 126 } 127 } 128 if oldR == nil { 129 return state, fmt.Errorf( 130 "Failed state verification, resource with ID %s not found", 131 r.Primary.ID) 132 } 133 134 // We'll try our best to find the schema for this resource type 135 // so we can ignore Removed fields during validation. If we fail 136 // to find the schema then we won't ignore them and so the test 137 // will need to rely on explicit ImportStateVerifyIgnore, though 138 // this shouldn't happen in any reasonable case. 139 var rsrcSchema *schema.Resource 140 if providerAddr, diags := addrs.ParseAbsProviderConfigStr(r.Provider); !diags.HasErrors() { 141 providerType := providerAddr.ProviderConfig.Type 142 if provider, ok := step.providers[providerType]; ok { 143 if provider, ok := provider.(*schema.Provider); ok { 144 rsrcSchema = provider.ResourcesMap[r.Type] 145 } 146 } 147 } 148 149 // don't add empty flatmapped containers, so we can more easily 150 // compare the attributes 151 skipEmpty := func(k, v string) bool { 152 if strings.HasSuffix(k, ".#") || strings.HasSuffix(k, ".%") { 153 if v == "0" { 154 return true 155 } 156 } 157 return false 158 } 159 160 // Compare their attributes 161 actual := make(map[string]string) 162 for k, v := range r.Primary.Attributes { 163 if skipEmpty(k, v) { 164 continue 165 } 166 actual[k] = v 167 } 168 169 expected := make(map[string]string) 170 for k, v := range oldR.Primary.Attributes { 171 if skipEmpty(k, v) { 172 continue 173 } 174 expected[k] = v 175 } 176 177 // Remove fields we're ignoring 178 for _, v := range step.ImportStateVerifyIgnore { 179 for k := range actual { 180 if strings.HasPrefix(k, v) { 181 delete(actual, k) 182 } 183 } 184 for k := range expected { 185 if strings.HasPrefix(k, v) { 186 delete(expected, k) 187 } 188 } 189 } 190 191 // Also remove any attributes that are marked as "Removed" in the 192 // schema, if we have a schema to check that against. 193 if rsrcSchema != nil { 194 for k := range actual { 195 for _, schema := range rsrcSchema.SchemasForFlatmapPath(k) { 196 if schema.Removed != "" { 197 delete(actual, k) 198 break 199 } 200 } 201 } 202 for k := range expected { 203 for _, schema := range rsrcSchema.SchemasForFlatmapPath(k) { 204 if schema.Removed != "" { 205 delete(expected, k) 206 break 207 } 208 } 209 } 210 } 211 212 if !reflect.DeepEqual(actual, expected) { 213 // Determine only the different attributes 214 for k, v := range expected { 215 if av, ok := actual[k]; ok && v == av { 216 delete(expected, k) 217 delete(actual, k) 218 } 219 } 220 221 spewConf := spew.NewDefaultConfig() 222 spewConf.SortKeys = true 223 return state, fmt.Errorf( 224 "ImportStateVerify attributes not equivalent. Difference is shown below. Top is actual, bottom is expected."+ 225 "\n\n%s\n\n%s", 226 spewConf.Sdump(actual), spewConf.Sdump(expected)) 227 } 228 } 229 } 230 231 // Return the old state (non-imported) so we don't change anything. 232 return state, nil 233 }