Skip to content

Commit ba065ff

Browse files
authored
internal/fromproto: Introduce conversion functions for proto schemas to FW schemas (#1200)
* nilness fixes * refactor dynamic type conversion * resource and identity schema conversions * bring docs over from other PR * update pkg docs
1 parent 0dfd722 commit ba065ff

40 files changed

+2398
-128
lines changed

action/doc.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
// Copyright (c) HashiCorp, Inc.
22
// SPDX-License-Identifier: MPL-2.0
33

4-
// TODO:Actions: Eventual package docs for actions
4+
// Package action contains all interfaces, request types, and response
5+
// types for an action implementation.
6+
//
7+
// In Terraform, an action is a concept which enables provider developers
8+
// to offer practitioners ad-hoc side-effects to be used in their configuration.
9+
//
10+
// The main starting point for implementations in this package is the
11+
// [Action] type which represents an instance of an action that has its
12+
// own configuration, plan, and invoke logic. The [Action] implementations
13+
// are referenced by the [provider.ProviderWithActions] type Actions method,
14+
// which enables the action practitioner usage.
515
package action

action/invoke.go

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,7 @@ type InvokeResponse struct {
2929
Diagnostics diag.Diagnostics
3030

3131
// SendProgress will immediately send a progress update to Terraform core during action invocation.
32-
// This function is provided by the framework and can be called multiple times while action logic is running.
33-
//
34-
// TODO:Actions: More documentation about when you should use this / when you shouldn't
32+
// This function is pre-populated by the framework and can be called multiple times while action logic is running.
3533
SendProgress func(event InvokeProgressEvent)
3634

3735
// TODO:Actions: Add linked resources once lifecycle/linked actions are implemented

action/schema/unlinked_schema_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,7 @@ func TestSchemaAttributeAtTerraformPath(t *testing.T) {
406406
return
407407
}
408408

409-
if err == nil && tc.expectedErr != "" {
409+
if tc.expectedErr != "" {
410410
t.Errorf("Expected error to be %q, got nil", tc.expectedErr)
411411
return
412412
}

datasource/schema/schema_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,7 @@ func TestSchemaAttributeAtTerraformPath(t *testing.T) {
406406
return
407407
}
408408

409-
if err == nil && tc.expectedErr != "" {
409+
if tc.expectedErr != "" {
410410
t.Errorf("Expected error to be %q, got nil", tc.expectedErr)
411411
return
412412
}

ephemeral/schema/schema_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,7 @@ func TestSchemaAttributeAtTerraformPath(t *testing.T) {
406406
return
407407
}
408408

409-
if err == nil && tc.expectedErr != "" {
409+
if tc.expectedErr != "" {
410410
t.Errorf("Expected error to be %q, got nil", tc.expectedErr)
411411
return
412412
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package fromproto5
5+
6+
import (
7+
"context"
8+
"fmt"
9+
10+
"github.com/hashicorp/terraform-plugin-framework/resource/identityschema"
11+
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
12+
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
13+
"github.com/hashicorp/terraform-plugin-go/tftypes"
14+
)
15+
16+
// IdentitySchema converts a *tfprotov5.ResourceIdentitySchema into a resource/identityschema Schema, used for
17+
// converting protocol identity schemas (from another provider server, such as SDKv2 or terraform-plugin-go)
18+
// into Framework identity schemas.
19+
func IdentitySchema(ctx context.Context, s *tfprotov5.ResourceIdentitySchema) (*identityschema.Schema, error) {
20+
if s == nil {
21+
return nil, nil
22+
}
23+
24+
attrs, err := IdentitySchemaAttributes(ctx, s.IdentityAttributes)
25+
if err != nil {
26+
return nil, err
27+
}
28+
29+
return &identityschema.Schema{
30+
// MAINTAINER NOTE: At the moment, there isn't a need to copy all of the data from the protocol identity schema
31+
// to the resource identity schema, just enough data to allow provider developers to read and set data.
32+
Attributes: attrs,
33+
}, nil
34+
}
35+
36+
func IdentitySchemaAttributes(ctx context.Context, protoAttrs []*tfprotov5.ResourceIdentitySchemaAttribute) (map[string]identityschema.Attribute, error) {
37+
attrs := make(map[string]identityschema.Attribute, len(protoAttrs))
38+
for _, protoAttr := range protoAttrs {
39+
// MAINTAINER NOTE: At the moment, there isn't a need to copy all of the data from the protocol identity schema
40+
// to the resource identity schema, just enough data to allow provider developers to read and set data.
41+
switch {
42+
case protoAttr.Type.Is(tftypes.Bool):
43+
attrs[protoAttr.Name] = identityschema.BoolAttribute{
44+
RequiredForImport: protoAttr.RequiredForImport,
45+
OptionalForImport: protoAttr.OptionalForImport,
46+
}
47+
case protoAttr.Type.Is(tftypes.Number):
48+
attrs[protoAttr.Name] = identityschema.NumberAttribute{
49+
RequiredForImport: protoAttr.RequiredForImport,
50+
OptionalForImport: protoAttr.OptionalForImport,
51+
}
52+
case protoAttr.Type.Is(tftypes.String):
53+
attrs[protoAttr.Name] = identityschema.StringAttribute{
54+
RequiredForImport: protoAttr.RequiredForImport,
55+
OptionalForImport: protoAttr.OptionalForImport,
56+
}
57+
case protoAttr.Type.Is(tftypes.List{}):
58+
//nolint:forcetypeassert // Type assertion is guaranteed by the above `(tftypes.Type).Is` function
59+
l := protoAttr.Type.(tftypes.List)
60+
61+
elementType, err := basetypes.TerraformTypeToFrameworkType(l.ElementType)
62+
if err != nil {
63+
return nil, err
64+
}
65+
66+
attrs[protoAttr.Name] = identityschema.ListAttribute{
67+
ElementType: elementType,
68+
RequiredForImport: protoAttr.RequiredForImport,
69+
OptionalForImport: protoAttr.OptionalForImport,
70+
}
71+
default:
72+
// MAINTAINER NOTE: Not all terraform types are valid identity attribute types. Framework fully supports
73+
// all of the possible identity attribute types, so any errors here would be invalid protocol identities.
74+
return nil, fmt.Errorf("no supported identity attribute for %q, type: %T", protoAttr.Name, protoAttr.Type)
75+
}
76+
}
77+
78+
return attrs, nil
79+
}
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package fromproto5_test
5+
6+
import (
7+
"context"
8+
"testing"
9+
10+
"github.com/google/go-cmp/cmp"
11+
"github.com/hashicorp/terraform-plugin-framework/internal/fromproto5"
12+
"github.com/hashicorp/terraform-plugin-framework/resource/identityschema"
13+
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
14+
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
15+
"github.com/hashicorp/terraform-plugin-go/tftypes"
16+
)
17+
18+
func TestIdentitySchema(t *testing.T) {
19+
t.Parallel()
20+
21+
testCases := map[string]struct {
22+
input *tfprotov5.ResourceIdentitySchema
23+
expected *identityschema.Schema
24+
expectedErr string
25+
}{
26+
"nil": {
27+
input: nil,
28+
expected: nil,
29+
},
30+
"no-attrs": {
31+
input: &tfprotov5.ResourceIdentitySchema{},
32+
expected: &identityschema.Schema{
33+
Attributes: make(map[string]identityschema.Attribute, 0),
34+
},
35+
},
36+
"primitives-attrs": {
37+
input: &tfprotov5.ResourceIdentitySchema{
38+
IdentityAttributes: []*tfprotov5.ResourceIdentitySchemaAttribute{
39+
{
40+
Name: "bool",
41+
Type: tftypes.Bool,
42+
RequiredForImport: true,
43+
},
44+
{
45+
Name: "number",
46+
Type: tftypes.Number,
47+
OptionalForImport: true,
48+
},
49+
{
50+
Name: "string",
51+
Type: tftypes.String,
52+
OptionalForImport: true,
53+
},
54+
},
55+
},
56+
expected: &identityschema.Schema{
57+
Attributes: map[string]identityschema.Attribute{
58+
"bool": identityschema.BoolAttribute{
59+
RequiredForImport: true,
60+
},
61+
"number": identityschema.NumberAttribute{
62+
OptionalForImport: true,
63+
},
64+
"string": identityschema.StringAttribute{
65+
OptionalForImport: true,
66+
},
67+
},
68+
},
69+
},
70+
"list-attr": {
71+
input: &tfprotov5.ResourceIdentitySchema{
72+
IdentityAttributes: []*tfprotov5.ResourceIdentitySchemaAttribute{
73+
{
74+
Name: "list_of_bools",
75+
Type: tftypes.List{ElementType: tftypes.Bool},
76+
RequiredForImport: true,
77+
},
78+
},
79+
},
80+
expected: &identityschema.Schema{
81+
Attributes: map[string]identityschema.Attribute{
82+
"list_of_bools": identityschema.ListAttribute{
83+
ElementType: basetypes.BoolType{},
84+
RequiredForImport: true,
85+
},
86+
},
87+
},
88+
},
89+
"map-error": {
90+
input: &tfprotov5.ResourceIdentitySchema{
91+
IdentityAttributes: []*tfprotov5.ResourceIdentitySchemaAttribute{
92+
{
93+
Name: "map_of_strings",
94+
Type: tftypes.Map{ElementType: tftypes.String},
95+
OptionalForImport: true,
96+
},
97+
},
98+
},
99+
expectedErr: `no supported identity attribute for "map_of_strings", type: tftypes.Map`,
100+
},
101+
"set-error": {
102+
input: &tfprotov5.ResourceIdentitySchema{
103+
IdentityAttributes: []*tfprotov5.ResourceIdentitySchemaAttribute{
104+
{
105+
Name: "set_of_strings",
106+
Type: tftypes.Set{ElementType: tftypes.String},
107+
OptionalForImport: true,
108+
},
109+
},
110+
},
111+
expectedErr: `no supported identity attribute for "set_of_strings", type: tftypes.Set`,
112+
},
113+
}
114+
115+
for name, tc := range testCases {
116+
t.Run(name, func(t *testing.T) {
117+
t.Parallel()
118+
119+
got, err := fromproto5.IdentitySchema(context.Background(), tc.input)
120+
if err != nil {
121+
if tc.expectedErr == "" {
122+
t.Errorf("Unexpected error: %s", err)
123+
return
124+
}
125+
if err.Error() != tc.expectedErr {
126+
t.Errorf("Expected error to be %q, got %q", tc.expectedErr, err.Error())
127+
return
128+
}
129+
// got expected error
130+
return
131+
}
132+
if tc.expectedErr != "" {
133+
t.Errorf("Expected error to be %q, got nil", tc.expectedErr)
134+
return
135+
}
136+
if diff := cmp.Diff(got, tc.expected); diff != "" {
137+
t.Errorf("Unexpected diff (+wanted, -got): %s", diff)
138+
return
139+
}
140+
})
141+
}
142+
}

0 commit comments

Comments
 (0)