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  }