Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
202 changes: 105 additions & 97 deletions helper/resource/testing_new_import_state.go
Original file line number Diff line number Diff line change
Expand Up @@ -296,138 +296,146 @@ func runImportTestStep(ctx context.Context, t testing.T, helper *plugintest.Help
if step.ImportStateVerify {
logging.HelperResourceTrace(ctx, "Using TestStep ImportStateVerify")

// Ensure that we do not match against data sources as they
// cannot be imported and are not what we want to verify.
// Mode is not present in ResourceState so we use the
// stringified ResourceStateKey for comparison.
newResources := make(map[string]*terraform.ResourceState)
for k, v := range importState.RootModule().Resources {
if !strings.HasPrefix(k, "data.") {
newResources[k] = v
}
}
oldResources := make(map[string]*terraform.ResourceState)
for k, v := range state.RootModule().Resources {
if !strings.HasPrefix(k, "data.") {
oldResources[k] = v
}
}

identifierAttribute := step.ImportStateVerifyIdentifierAttribute
importStateVerifyIgnore := step.ImportStateVerifyIgnore

if identifierAttribute == "" {
identifierAttribute = "id"
if outcome, err := verifyImportState(state, importState, identifierAttribute, importStateVerifyIgnore); !outcome.ok {
return outcome, err
}
}

for _, r := range newResources {
rIdentifier, ok := r.Primary.Attributes[identifierAttribute]
return testOK()
}

if !ok {
return testFatal(fmt.Errorf("ImportStateVerify: New resource missing identifier attribute %q, ensure attribute value is properly set or use ImportStateVerifyIdentifierAttribute to choose different attribute", identifierAttribute))
}
func verifyImportState(state *terraform.State, importState *terraform.State, identifierAttribute string, importStateVerifyIgnore []string) (testOutcome, error) {
// Ensure that we do not match against data sources as they
// cannot be imported and are not what we want to verify.
// Mode is not present in ResourceState so we use the
// stringified ResourceStateKey for comparison.
newResources := make(map[string]*terraform.ResourceState)
for k, v := range importState.RootModule().Resources {
if !strings.HasPrefix(k, "data.") {
newResources[k] = v
}
}
oldResources := make(map[string]*terraform.ResourceState)
for k, v := range state.RootModule().Resources {
if !strings.HasPrefix(k, "data.") {
oldResources[k] = v
}
}

// Find the existing resource
var oldR *terraform.ResourceState
for _, r2 := range oldResources {
if r2.Primary == nil || r2.Type != r.Type || r2.Provider != r.Provider {
continue
}
if identifierAttribute == "" {
identifierAttribute = "id"
}

r2Identifier, ok := r2.Primary.Attributes[identifierAttribute]
for _, r := range newResources {
rIdentifier, ok := r.Primary.Attributes[identifierAttribute]

if !ok {
return testFatal(fmt.Errorf("ImportStateVerify: Old resource missing identifier attribute %q, ensure attribute value is properly set or use ImportStateVerifyIdentifierAttribute to choose different attribute", identifierAttribute))
}
if !ok {
return testFatal(fmt.Errorf("ImportStateVerify: New resource missing identifier attribute %q, ensure attribute value is properly set or use ImportStateVerifyIdentifierAttribute to choose different attribute", identifierAttribute))
}

if r2Identifier == rIdentifier {
oldR = r2
break
}
// Find the existing resource
var oldR *terraform.ResourceState
for _, r2 := range oldResources {
if r2.Primary == nil || r2.Type != r.Type || r2.Provider != r.Provider {
continue
}
if oldR == nil || oldR.Primary == nil {
return testFatal(fmt.Errorf("Failed state verification, resource with ID %s not found", rIdentifier))

r2Identifier, ok := r2.Primary.Attributes[identifierAttribute]

if !ok {
return testFatal(fmt.Errorf("ImportStateVerify: Old resource missing identifier attribute %q, ensure attribute value is properly set or use ImportStateVerifyIdentifierAttribute to choose different attribute", identifierAttribute))
}

// don't add empty flatmapped containers, so we can more easily
// compare the attributes
skipEmpty := func(k, v string) bool {
if strings.HasSuffix(k, ".#") || strings.HasSuffix(k, ".%") {
if v == "0" {
return true
}
}
return false
if r2Identifier == rIdentifier {
oldR = r2
break
}
}
if oldR == nil || oldR.Primary == nil {
return testFatal(fmt.Errorf("Failed state verification, resource with ID %s not found", rIdentifier))
}

// Compare their attributes
actual := make(map[string]string)
for k, v := range r.Primary.Attributes {
if skipEmpty(k, v) {
continue
// don't add empty flatmapped containers, so we can more easily
// compare the attributes
skipEmpty := func(k, v string) bool {
if strings.HasSuffix(k, ".#") || strings.HasSuffix(k, ".%") {
if v == "0" {
return true
}
actual[k] = v
}
return false
}

expected := make(map[string]string)
for k, v := range oldR.Primary.Attributes {
if skipEmpty(k, v) {
continue
}
expected[k] = v
// Compare their attributes
actual := make(map[string]string)
for k, v := range r.Primary.Attributes {
if skipEmpty(k, v) {
continue
}
actual[k] = v
}

// Remove fields we're ignoring
for _, v := range step.ImportStateVerifyIgnore {
for k := range actual {
if strings.HasPrefix(k, v) {
delete(actual, k)
}
}
for k := range expected {
if strings.HasPrefix(k, v) {
delete(expected, k)
}
}
expected := make(map[string]string)
for k, v := range oldR.Primary.Attributes {
if skipEmpty(k, v) {
continue
}
expected[k] = v
}

// timeouts are only _sometimes_ added to state. To
// account for this, just don't compare timeouts at
// all.
// Remove fields we're ignoring
for _, v := range importStateVerifyIgnore {
for k := range actual {
if strings.HasPrefix(k, "timeouts.") {
delete(actual, k)
}
if k == "timeouts" {
if strings.HasPrefix(k, v) {
delete(actual, k)
}
}
for k := range expected {
if strings.HasPrefix(k, "timeouts.") {
delete(expected, k)
}
if k == "timeouts" {
if strings.HasPrefix(k, v) {
delete(expected, k)
}
}
}

if !reflect.DeepEqual(actual, expected) {
// Determine only the different attributes
// go-cmp tries to show surrounding identical map key/value for
// context of differences, which may be confusing.
for k, v := range expected {
if av, ok := actual[k]; ok && v == av {
delete(expected, k)
delete(actual, k)
}
}
// timeouts are only _sometimes_ added to state. To
// account for this, just don't compare timeouts at
// all.
for k := range actual {
if strings.HasPrefix(k, "timeouts.") {
delete(actual, k)
}
if k == "timeouts" {
delete(actual, k)
}
}
for k := range expected {
if strings.HasPrefix(k, "timeouts.") {
delete(expected, k)
}
if k == "timeouts" {
delete(expected, k)
}
}

if diff := cmp.Diff(expected, actual); diff != "" {
return testFail("ImportStateVerify attributes not equivalent. Difference is shown below. The - symbol indicates attributes missing after import.\n\n%s", diff)
if !reflect.DeepEqual(actual, expected) {
// Determine only the different attributes
// go-cmp tries to show surrounding identical map key/value for
// context of differences, which may be confusing.
for k, v := range expected {
if av, ok := actual[k]; ok && v == av {
delete(expected, k)
delete(actual, k)
}
}

if diff := cmp.Diff(expected, actual); diff != "" {
return testFail("ImportStateVerify attributes not equivalent. Difference is shown below. The - symbol indicates attributes missing after import.\n\n%s", diff)
}
}
}

return testOK()
}

Expand Down
Loading