Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
2 changes: 1 addition & 1 deletion helper/schema/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -505,7 +505,7 @@ func (p *Provider) ImportStateWithIdentity(
if err != nil {
return nil, err // this should not happen, as we checked above
}
identityData.raw = identity // is this too hacky / unexpected?
identityData.raw = identity
} else if identity != nil {
return nil, fmt.Errorf("resource %s doesn't support identity import", info.Type)
}
Expand Down
28 changes: 28 additions & 0 deletions helper/schema/resource_importer.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package schema
import (
"context"
"errors"
"fmt"
)

// ResourceImporter defines how a resource is imported in Terraform. This
Expand Down Expand Up @@ -83,3 +84,30 @@ func ImportStatePassthrough(d *ResourceData, m interface{}) ([]*ResourceData, er
func ImportStatePassthroughContext(ctx context.Context, d *ResourceData, m interface{}) ([]*ResourceData, error) {
return []*ResourceData{d}, nil
}

func ImportStatePassthroughWithIdentity(idAttributePath string) StateContextFunc {
return func(ctx context.Context, d *ResourceData, m interface{}) ([]*ResourceData, error) {
// If we import by id, we just return the resource data as is, no need to change it
if d.Id() != "" {
return []*ResourceData{d}, nil
}

// If we import by identity, we need to set the id based on the idAttributePath
identity, err := d.Identity()
if err != nil {
return nil, fmt.Errorf("error getting identity: %s", err)
}
id, exists := identity.GetOk(idAttributePath)
if !exists {
return nil, fmt.Errorf("expected identity to contain key %s", idAttributePath)
}
idStr, ok := id.(string)
if !ok {
return nil, fmt.Errorf("expected identity key %s to be a string, was: %T", idAttributePath, id)
}

d.SetId(idStr)

return []*ResourceData{d}, nil
}
}
171 changes: 170 additions & 1 deletion helper/schema/resource_importer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@

package schema

import "testing"
import (
"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
)

func TestInternalValidate(t *testing.T) {
r := &ResourceImporter{
Expand All @@ -14,3 +18,168 @@ func TestInternalValidate(t *testing.T) {
t.Fatal("ResourceImporter should not allow State and StateContext to be set")
}
}

func TestImportStatePassthroughWithIdentity(t *testing.T) {
// shared among all tests, defined once to keep them shorter
identitySchema := map[string]*Schema{
"email": {
Type: TypeString,
RequiredForImport: true,
},
"region": {
Type: TypeString,
OptionalForImport: true,
},
}

tests := []struct {
name string
idAttributePath string
resourceData *ResourceData
expectedResourceData *ResourceData
expectedError string
}{
{
name: "import from id just sets id",
idAttributePath: "email",
resourceData: &ResourceData{
identitySchema: identitySchema,
state: &terraform.InstanceState{
ID: "hello@example.internal",
},
},
expectedResourceData: &ResourceData{
identitySchema: identitySchema,
state: &terraform.InstanceState{
ID: "hello@example.internal",
},
},
},
{
name: "import from identity sets id and identity",
idAttributePath: "email",
resourceData: &ResourceData{
identitySchema: identitySchema,
state: &terraform.InstanceState{
Identity: map[string]string{
"email": "hello@example.internal",
},
},
},
expectedResourceData: &ResourceData{
identitySchema: identitySchema,
state: &terraform.InstanceState{
ID: "hello@example.internal",
},
newIdentity: &IdentityData{
schema: identitySchema,
raw: map[string]string{
"email": "hello@example.internal",
},
},
},
},
{
name: "import from identity sets id and identity (with region set)",
idAttributePath: "email",
resourceData: &ResourceData{
identitySchema: identitySchema,
state: &terraform.InstanceState{
Identity: map[string]string{
"email": "hello@example.internal",
"region": "eu-west-1",
},
},
},
expectedResourceData: &ResourceData{
identitySchema: identitySchema,
state: &terraform.InstanceState{
ID: "hello@example.internal",
},
newIdentity: &IdentityData{
schema: identitySchema,
raw: map[string]string{
"email": "hello@example.internal",
"region": "eu-west-1",
},
},
},
},
{
name: "import from identity fails without required field",
idAttributePath: "email",
resourceData: &ResourceData{
identitySchema: identitySchema,
state: &terraform.InstanceState{
Identity: map[string]string{
"region": "eu-west-1",
},
},
},
expectedError: "expected identity to contain key email",
},
{
name: "import from identity fails without schema",
idAttributePath: "email",
resourceData: &ResourceData{
state: &terraform.InstanceState{
Identity: map[string]string{
"email": "hello@example.internal",
},
},
},
expectedError: "error getting identity: Resource does not have Identity schema. Please set one in order to use Identity(). This is always a problem in the provider code.",
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
results, err := ImportStatePassthroughWithIdentity(test.idAttributePath)(nil, test.resourceData, nil)
if err != nil {
if test.expectedError == "" {
t.Fatalf("unexpected error: %s", err)
}
if err.Error() != test.expectedError {
t.Fatalf("expected error: %s, got: %s", test.expectedError, err)
}
return // we don't expect any results if there is an error
}
if len(results) != 1 {
t.Fatalf("expected 1 result, got: %d", len(results))
}
// compare id and identity in resource data
if results[0].Id() != test.expectedResourceData.Id() {
t.Fatalf("expected id: %s, got: %s", test.expectedResourceData.Id(), results[0].Id())
}
// compare identity
expectedIdentity, err := test.expectedResourceData.Identity()
if err != nil {
t.Fatalf("unexpected error: %s", err)
}
resultIdentity, err := results[0].Identity()
if err != nil {
t.Fatalf("unexpected error: %s", err)
}

// check whether all result identity attributes exist as expected
for key := range expectedIdentity.schema {
expected := expectedIdentity.getRaw(key)
if expected.Exists {
result := resultIdentity.getRaw(key)
if !result.Exists {
t.Fatalf("expected identity attribute %s to exist", key)
}
if expected.Value != result.Value {
t.Fatalf("expected identity attribute %s to be %s, got: %s", key, expected.Value, result.Value)
}
}
}
// check whether there are no additional attributes in the result identity
for key := range resultIdentity.schema {
if _, ok := expectedIdentity.schema[key]; !ok {
t.Fatalf("unexpected identity attribute %s", key)
}
}
})
}
}
Loading