Skip to content

Commit

Permalink
initial import support
Browse files Browse the repository at this point in the history
  • Loading branch information
anGie44 committed Sep 16, 2021
1 parent 2c068eb commit 974165c
Show file tree
Hide file tree
Showing 6 changed files with 296 additions and 11 deletions.
26 changes: 15 additions & 11 deletions internal/generic/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
"reflect"
"strings"
"time"

Expand Down Expand Up @@ -723,21 +724,24 @@ func (r *resource) ImportState(ctx context.Context, request tfsdk.ImportResource

tflog.Debug(ctx, "Request.ID", "value", hclog.Fmt("%v", request.ID))

m := map[string]tftypes.Value{
"id": tftypes.NewValue(tftypes.String, request.ID),
translator := toStruct{Schema: r.resourceType.tfSchema}
typ, err := translator.FromSchemaAttributes(ctx)
if err != nil {
response.Diagnostics.AddError(
"Import Of Terraform State Unsuccessful",
fmt.Sprintf("Unable to create a Terraform State value from a tfsdk.Schema for import. This is typically an error with the Terraform provider implementation. Original Error: %s", err.Error()),
)

return
}

id := tftypes.NewValue(tftypes.Object{
AttributeTypes: map[string]tftypes.Type{
"id": tftypes.String,
}}, m)
state := reflect.New(typ)

response.State = tfsdk.State{
Schema: r.resourceType.tfSchema,
Raw: id,
}
state.Elem().FieldByName("Id").SetString(request.ID)

diags := response.State.Set(ctx, state.Elem().Interface())

//tfsdk.ResourceImportStatePassthroughID(ctx, idAttributePath, request, response)
response.Diagnostics.Append(diags...)

tflog.Trace(ctx, "Resource.ImportState exit", "cfTypeName", r.resourceType.cfTypeName, "tfTypeName", r.resourceType.tfTypeName)
}
Expand Down
20 changes: 20 additions & 0 deletions internal/generic/state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,26 @@ var testSimpleSchemaWithList = tfsdk.Schema{
},
}

var testSimpleSchemaWithUnsupportedType = tfsdk.Schema{
Attributes: map[string]tfsdk.Attribute{
"arn": {
Computed: true,
},
"identifier": {
Type: types.StringType,
Computed: true,
},
"name": {
Type: types.StringType,
Required: true,
},
"number": {
Type: types.NumberType,
Optional: true,
},
},
}

var simpleCfToTfNameMap = map[string]string{
"Arn": "arn",
"Identifier": "identifier",
Expand Down
41 changes: 41 additions & 0 deletions internal/generic/translate.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ import (
"encoding/json"
"fmt"
"math/big"
"reflect"

"github.com/hashicorp/terraform-plugin-framework/tfsdk"
"github.com/hashicorp/terraform-plugin-go/tftypes"
tflog "github.com/hashicorp/terraform-plugin-log"
"github.com/hashicorp/terraform-provider-awscc/internal/naming"
)

// Translates a Terraform Value to CloudFormation DesiredState.
Expand Down Expand Up @@ -269,3 +271,42 @@ func (t toTerraform) valueFromRaw(ctx context.Context, schema *tfsdk.Schema, pat
return tftypes.Value{}, fmt.Errorf("unsupported raw type: %T", v)
}
}

type toStruct struct {
Schema tfsdk.Schema
}

func (s toStruct) FromSchemaAttributes(ctx context.Context) (reflect.Type, error) {
var fields []reflect.StructField

for k, v := range s.Schema.Attributes {
name := naming.TerraformAttributeToPascalCase(k)

field := reflect.StructField{
Name: name,
Tag: reflect.StructTag(fmt.Sprintf(`tfsdk:"%s"`, k)),
}

if v.Type != nil {
if v.Type.TerraformType(ctx).Is(tftypes.String) {
field.Type = reflect.TypeOf("")
} else if v.Type.TerraformType(ctx).Is(tftypes.Number) {
field.Type = reflect.TypeOf(0.0)
} else if v.Type.TerraformType(ctx).Is(tftypes.Bool) {
field.Type = reflect.TypeOf(false)
} else if v.Type.TerraformType(ctx).Is(tftypes.Set{}) || v.Type.TerraformType(ctx).Is(tftypes.List{}) || v.Type.TerraformType(ctx).Is(tftypes.Tuple{}) {
field.Type = reflect.TypeOf([]interface{}{})
} else if v.Type.TerraformType(ctx).Is(tftypes.Map{}) || v.Type.TerraformType(ctx).Is(tftypes.Object{}) {
field.Type = reflect.TypeOf(map[string]interface{}{})
}
} else if v.Attributes != nil { // attribute is a not a "tftype" e.g. providertypes.SetNestedAttributes
field.Type = reflect.TypeOf([]interface{}{})
} else {
return nil, fmt.Errorf("unknown type for attribute: %T", v)
}

fields = append(fields, field)
}

return reflect.StructOf(fields), nil
}
175 changes: 175 additions & 0 deletions internal/generic/translate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package generic

import (
"context"
"reflect"
"testing"

"github.com/google/go-cmp/cmp"
Expand Down Expand Up @@ -392,3 +393,177 @@ func TestTranslateToTerraform(t *testing.T) {
})
}
}

func TestTranslateToStruct(t *testing.T) {
testCases := []struct {
TestName string
Schema tfsdk.Schema
ExpectedType reflect.Type
ExpectedError bool
}{
{
TestName: "simple struct with unsupported attribute",
Schema: testSimpleSchemaWithUnsupportedType,
ExpectedError: true,
},
{
TestName: "simple Struct",
Schema: testSimpleSchema,
ExpectedType: reflect.StructOf([]reflect.StructField{
{
Name: "Arn",
Tag: `tfsdk:"arn"`,
Type: reflect.TypeOf(""),
},
{
Name: "Identifier",
Tag: `tfsdk:"identifier"`,
Type: reflect.TypeOf(""),
},
{
Name: "Name",
Tag: `tfsdk:"name"`,
Type: reflect.TypeOf(""),
},
{
Name: "Number",
Tag: `tfsdk:"number"`,
Type: reflect.TypeOf(0.0),
},
}),
},
{
TestName: "simple struct with list",
Schema: testSimpleSchemaWithList,
ExpectedType: reflect.StructOf([]reflect.StructField{
{
Name: "Arn",
Tag: `tfsdk:"arn"`,
Type: reflect.TypeOf(""),
},
{
Name: "Identifier",
Tag: `tfsdk:"identifier"`,
Type: reflect.TypeOf(""),
},
{
Name: "Name",
Tag: `tfsdk:"name"`,
Type: reflect.TypeOf(""),
},
{
Name: "Number",
Tag: `tfsdk:"number"`,
Type: reflect.TypeOf(0.0),
},
{
Name: "Ports",
Tag: `tfsdk:"ports"`,
Type: reflect.TypeOf([]interface{}{}),
},
}),
},
{
TestName: "complex struct",
Schema: testComplexSchema,
ExpectedType: reflect.StructOf([]reflect.StructField{
{
Name: "Name",
Tag: `tfsdk:"name"`,
Type: reflect.TypeOf(""),
},
{
Name: "MachineType",
Tag: `tfsdk:"machine_type"`,
Type: reflect.TypeOf(""),
},
{
Name: "Ports",
Tag: `tfsdk:"ports"`,
Type: reflect.TypeOf([]interface{}{}),
},
{
Name: "Tags",
Tag: `tfsdk:"tags"`,
Type: reflect.TypeOf([]interface{}{}),
},
{
Name: "Disks",
Tag: `tfsdk:"disks"`,
Type: reflect.TypeOf([]interface{}{}),
},
{
Name: "BootDisk",
Tag: `tfsdk:"boot_disk"`,
Type: reflect.TypeOf([]interface{}{}),
},
{
Name: "ScratchDisk",
Tag: `tfsdk:"scratch_disk"`,
Type: reflect.TypeOf(map[string]interface{}{}),
},
{
Name: "VideoPorts",
Tag: `tfsdk:"video_ports"`,
Type: reflect.TypeOf([]interface{}{}),
},
{
Name: "Identifier",
Tag: `tfsdk:"identifier"`,
Type: reflect.TypeOf(""),
},
}),
},
}

for _, testCase := range testCases {
t.Run(testCase.TestName, func(t *testing.T) {
translator := toStruct{Schema: testCase.Schema}
got, err := translator.FromSchemaAttributes(context.TODO())

if err == nil && testCase.ExpectedError {
t.Fatalf("expected error")
}

if err != nil && !testCase.ExpectedError {
t.Fatalf("unexpected error: %s", err)
}

if err == nil {
if !typesEqual(testCase.ExpectedType, got) {
t.Errorf("expected: %v, got: %v", testCase.ExpectedType, got)
}
}
})
}
}

func typesEqual(expected, got reflect.Type) bool {
if got.NumField() != expected.NumField() {
return false
}

for i := 0; i < expected.NumField(); i++ {
expectedField := expected.Field(i)

gotField, ok := got.FieldByName(expectedField.Name)

if !ok {
return false
}

if gotField.Name != expectedField.Name {
return false
}

if gotField.Tag != expectedField.Tag {
return false
}

if gotField.Type != expectedField.Type {
return false
}
}

return true
}
6 changes: 6 additions & 0 deletions internal/naming/naming.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,12 @@ func Pluralize(name string) string {
return pluralName
}

// For example `global_replication_group_description` -> `GlobalReplicationGroupDescription`.
func TerraformAttributeToPascalCase(name string) string {
n := strings.Replace(name, "_", " ", -1)
return strings.Replace(strings.Title(n), " ", "", -1)
}

// PluralizeWithCustomNameSuffix converts a name to its plural form similar to Pluralize,
// with the exception that a suffix can be passed in as an argument to be used
// only for names that are considered "custom" i.e. return true for isCustomName.
Expand Down
39 changes: 39 additions & 0 deletions internal/naming/naming_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,3 +188,42 @@ func TestPluralizeWithCustomNameSuffix(t *testing.T) {
})
}
}

func TestTerraformAttributeToPascalCase(t *testing.T) {
testCases := []struct {
TestName string
Name string
ExpectedValue string
}{
{
TestName: "empty string",
Name: "",
ExpectedValue: "",
},
{
TestName: "without underscore",
Name: "arn",
ExpectedValue: "Arn",
},
{
TestName: "underscored terraform type",
Name: "aws_example_association",
ExpectedValue: "AwsExampleAssociation",
},
{
TestName: "hyphened terraform type",
Name: "aws-example-association",
ExpectedValue: "Aws-Example-Association",
},
}

for _, testCase := range testCases {
t.Run(testCase.TestName, func(t *testing.T) {
got := naming.TerraformAttributeToPascalCase(testCase.Name)

if got != testCase.ExpectedValue {
t.Errorf("expected: %s, got: %s", testCase.ExpectedValue, got)
}
})
}
}

0 comments on commit 974165c

Please sign in to comment.