Files
opentf/helper/resource/testing_import_state.go
Martin Atkins 39e609d5fd vendor: switch to HCL 2.0 in the HCL repository
Previously we were using the experimental HCL 2 repository, but now we'll
shift over to the v2 import path within the main HCL repository as part of
actually releasing HCL 2.0 as stable.

This is a mechanical search/replace to the new import paths. It also
switches to the v2.0.0 release of HCL, which includes some new code that
Terraform didn't previously have but should not change any behavior that
matters for Terraform's purposes.

For the moment the experimental HCL2 repository is still an indirect
dependency via terraform-config-inspect, so it remains in our go.sum and
vendor directories for the moment. Because terraform-config-inspect uses
a much smaller subset of the HCL2 functionality, this does still manage
to prune the vendor directory a little. A subsequent release of
terraform-config-inspect should allow us to completely remove that old
repository in a future commit.
2019-10-02 15:10:21 -07:00

234 lines
6.0 KiB
Go

package resource
import (
"fmt"
"log"
"reflect"
"strings"
"github.com/davecgh/go-spew/spew"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/hcl/v2/hclsyntax"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/helper/schema"
"github.com/hashicorp/terraform/states"
"github.com/hashicorp/terraform/terraform"
)
// testStepImportState runs an imort state test step
func testStepImportState(
opts terraform.ContextOpts,
state *terraform.State,
step TestStep) (*terraform.State, error) {
// Determine the ID to import
var importId string
switch {
case step.ImportStateIdFunc != nil:
var err error
importId, err = step.ImportStateIdFunc(state)
if err != nil {
return state, err
}
case step.ImportStateId != "":
importId = step.ImportStateId
default:
resource, err := testResource(step, state)
if err != nil {
return state, err
}
importId = resource.Primary.ID
}
importPrefix := step.ImportStateIdPrefix
if importPrefix != "" {
importId = fmt.Sprintf("%s%s", importPrefix, importId)
}
// Setup the context. We initialize with an empty state. We use the
// full config for provider configurations.
cfg, err := testConfig(opts, step)
if err != nil {
return state, err
}
opts.Config = cfg
// import tests start with empty state
opts.State = states.NewState()
ctx, stepDiags := terraform.NewContext(&opts)
if stepDiags.HasErrors() {
return state, stepDiags.Err()
}
// The test step provides the resource address as a string, so we need
// to parse it to get an addrs.AbsResourceAddress to pass in to the
// import method.
traversal, hclDiags := hclsyntax.ParseTraversalAbs([]byte(step.ResourceName), "", hcl.Pos{})
if hclDiags.HasErrors() {
return nil, hclDiags
}
importAddr, stepDiags := addrs.ParseAbsResourceInstance(traversal)
if stepDiags.HasErrors() {
return nil, stepDiags.Err()
}
// Do the import
importedState, stepDiags := ctx.Import(&terraform.ImportOpts{
// Set the module so that any provider config is loaded
Config: cfg,
Targets: []*terraform.ImportTarget{
&terraform.ImportTarget{
Addr: importAddr,
ID: importId,
},
},
})
if stepDiags.HasErrors() {
log.Printf("[ERROR] Test: ImportState failure: %s", stepDiags.Err())
return state, stepDiags.Err()
}
newState, err := shimNewState(importedState, step.providers)
if err != nil {
return nil, err
}
// Go through the new state and verify
if step.ImportStateCheck != nil {
var states []*terraform.InstanceState
for _, r := range newState.RootModule().Resources {
if r.Primary != nil {
is := r.Primary.DeepCopy()
is.Ephemeral.Type = r.Type // otherwise the check function cannot see the type
states = append(states, is)
}
}
if err := step.ImportStateCheck(states); err != nil {
return state, err
}
}
// Verify that all the states match
if step.ImportStateVerify {
new := newState.RootModule().Resources
old := state.RootModule().Resources
for _, r := range new {
// Find the existing resource
var oldR *terraform.ResourceState
for _, r2 := range old {
if r2.Primary != nil && r2.Primary.ID == r.Primary.ID && r2.Type == r.Type {
oldR = r2
break
}
}
if oldR == nil {
return state, fmt.Errorf(
"Failed state verification, resource with ID %s not found",
r.Primary.ID)
}
// We'll try our best to find the schema for this resource type
// so we can ignore Removed fields during validation. If we fail
// to find the schema then we won't ignore them and so the test
// will need to rely on explicit ImportStateVerifyIgnore, though
// this shouldn't happen in any reasonable case.
var rsrcSchema *schema.Resource
if providerAddr, diags := addrs.ParseAbsProviderConfigStr(r.Provider); !diags.HasErrors() {
providerType := providerAddr.ProviderConfig.Type
if provider, ok := step.providers[providerType]; ok {
if provider, ok := provider.(*schema.Provider); ok {
rsrcSchema = provider.ResourcesMap[r.Type]
}
}
}
// 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
}
// Compare their attributes
actual := make(map[string]string)
for k, v := range r.Primary.Attributes {
if skipEmpty(k, v) {
continue
}
actual[k] = v
}
expected := make(map[string]string)
for k, v := range oldR.Primary.Attributes {
if skipEmpty(k, v) {
continue
}
expected[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)
}
}
}
// Also remove any attributes that are marked as "Removed" in the
// schema, if we have a schema to check that against.
if rsrcSchema != nil {
for k := range actual {
for _, schema := range rsrcSchema.SchemasForFlatmapPath(k) {
if schema.Removed != "" {
delete(actual, k)
break
}
}
}
for k := range expected {
for _, schema := range rsrcSchema.SchemasForFlatmapPath(k) {
if schema.Removed != "" {
delete(expected, k)
break
}
}
}
}
if !reflect.DeepEqual(actual, expected) {
// Determine only the different attributes
for k, v := range expected {
if av, ok := actual[k]; ok && v == av {
delete(expected, k)
delete(actual, k)
}
}
spewConf := spew.NewDefaultConfig()
spewConf.SortKeys = true
return state, fmt.Errorf(
"ImportStateVerify attributes not equivalent. Difference is shown below. Top is actual, bottom is expected."+
"\n\n%s\n\n%s",
spewConf.Sdump(actual), spewConf.Sdump(expected))
}
}
}
// Return the old state (non-imported) so we don't change anything.
return state, nil
}