Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
12 changes: 11 additions & 1 deletion action/doc.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

// TODO:Actions: Eventual package docs for actions
// Package action contains all interfaces, request types, and response
// types for an action implementation.
//
// In Terraform, an action is a concept which enables provider developers
// to offer practitioners ad-hoc side-effects to be used in their configuration.
//
// The main starting point for implementations in this package is the
// [Action] type which represents an instance of an action that has its
// own configuration, plan, and invoke logic. The [Action] implementations
// are referenced by the [provider.ProviderWithActions] type Actions method,
// which enables the action practitioner usage.
package action
4 changes: 1 addition & 3 deletions action/invoke.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,7 @@ type InvokeResponse struct {
Diagnostics diag.Diagnostics

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

// TODO:Actions: Add linked resources once lifecycle/linked actions are implemented
Expand Down
2 changes: 1 addition & 1 deletion action/schema/unlinked_schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,7 @@ func TestSchemaAttributeAtTerraformPath(t *testing.T) {
return
}

if err == nil && tc.expectedErr != "" {
if tc.expectedErr != "" {
t.Errorf("Expected error to be %q, got nil", tc.expectedErr)
return
}
Expand Down
2 changes: 1 addition & 1 deletion datasource/schema/schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,7 @@ func TestSchemaAttributeAtTerraformPath(t *testing.T) {
return
}

if err == nil && tc.expectedErr != "" {
if tc.expectedErr != "" {
t.Errorf("Expected error to be %q, got nil", tc.expectedErr)
return
}
Expand Down
2 changes: 1 addition & 1 deletion ephemeral/schema/schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,7 @@ func TestSchemaAttributeAtTerraformPath(t *testing.T) {
return
}

if err == nil && tc.expectedErr != "" {
if tc.expectedErr != "" {
t.Errorf("Expected error to be %q, got nil", tc.expectedErr)
return
}
Expand Down
79 changes: 79 additions & 0 deletions internal/fromproto5/identity_schema.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package fromproto5

import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-framework/resource/identityschema"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
"github.com/hashicorp/terraform-plugin-go/tftypes"
)

// IdentitySchema converts a *tfprotov5.ResourceIdentitySchema into a resource/identityschema Schema, used for
// converting protocol identity schemas (from another provider server, such as SDKv2 or terraform-plugin-go)
// into Framework identity schemas.
func IdentitySchema(ctx context.Context, s *tfprotov5.ResourceIdentitySchema) (*identityschema.Schema, error) {
if s == nil {
return nil, nil
}

attrs, err := IdentitySchemaAttributes(ctx, s.IdentityAttributes)
if err != nil {
return nil, err
}

return &identityschema.Schema{
// MAINTAINER NOTE: At the moment, there isn't a need to copy all of the data from the protocol identity schema
// to the resource identity schema, just enough data to allow provider developers to read and set data.
Attributes: attrs,
}, nil
}

func IdentitySchemaAttributes(ctx context.Context, protoAttrs []*tfprotov5.ResourceIdentitySchemaAttribute) (map[string]identityschema.Attribute, error) {
attrs := make(map[string]identityschema.Attribute, len(protoAttrs))
for _, protoAttr := range protoAttrs {
// MAINTAINER NOTE: At the moment, there isn't a need to copy all of the data from the protocol identity schema
// to the resource identity schema, just enough data to allow provider developers to read and set data.
switch {
case protoAttr.Type.Is(tftypes.Bool):
attrs[protoAttr.Name] = identityschema.BoolAttribute{
RequiredForImport: protoAttr.RequiredForImport,
OptionalForImport: protoAttr.OptionalForImport,
}
case protoAttr.Type.Is(tftypes.Number):
attrs[protoAttr.Name] = identityschema.NumberAttribute{
RequiredForImport: protoAttr.RequiredForImport,
OptionalForImport: protoAttr.OptionalForImport,
}
case protoAttr.Type.Is(tftypes.String):
attrs[protoAttr.Name] = identityschema.StringAttribute{
RequiredForImport: protoAttr.RequiredForImport,
OptionalForImport: protoAttr.OptionalForImport,
}
case protoAttr.Type.Is(tftypes.List{}):
//nolint:forcetypeassert // Type assertion is guaranteed by the above `(tftypes.Type).Is` function
l := protoAttr.Type.(tftypes.List)

elementType, err := basetypes.TerraformTypeToFrameworkType(l.ElementType)
if err != nil {
return nil, err
}

attrs[protoAttr.Name] = identityschema.ListAttribute{
ElementType: elementType,
RequiredForImport: protoAttr.RequiredForImport,
OptionalForImport: protoAttr.OptionalForImport,
}
default:
// MAINTAINER NOTE: Not all terraform types are valid identity attribute types. Framework fully supports
// all of the possible identity attribute types, so any errors here would be invalid protocol identities.
return nil, fmt.Errorf("no supported identity attribute for %q, type: %T", protoAttr.Name, protoAttr.Type)
}
}

return attrs, nil
}
142 changes: 142 additions & 0 deletions internal/fromproto5/identity_schema_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package fromproto5_test

import (
"context"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform-plugin-framework/internal/fromproto5"
"github.com/hashicorp/terraform-plugin-framework/resource/identityschema"
"github.com/hashicorp/terraform-plugin-framework/types/basetypes"
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
"github.com/hashicorp/terraform-plugin-go/tftypes"
)

func TestIdentitySchema(t *testing.T) {
t.Parallel()

testCases := map[string]struct {
input *tfprotov5.ResourceIdentitySchema
expected *identityschema.Schema
expectedErr string
}{
"nil": {
input: nil,
expected: nil,
},
"no-attrs": {
input: &tfprotov5.ResourceIdentitySchema{},
expected: &identityschema.Schema{
Attributes: make(map[string]identityschema.Attribute, 0),
},
},
"primitives-attrs": {
input: &tfprotov5.ResourceIdentitySchema{
IdentityAttributes: []*tfprotov5.ResourceIdentitySchemaAttribute{
{
Name: "bool",
Type: tftypes.Bool,
RequiredForImport: true,
},
{
Name: "number",
Type: tftypes.Number,
OptionalForImport: true,
},
{
Name: "string",
Type: tftypes.String,
OptionalForImport: true,
},
},
},
expected: &identityschema.Schema{
Attributes: map[string]identityschema.Attribute{
"bool": identityschema.BoolAttribute{
RequiredForImport: true,
},
"number": identityschema.NumberAttribute{
OptionalForImport: true,
},
"string": identityschema.StringAttribute{
OptionalForImport: true,
},
},
},
},
"list-attr": {
input: &tfprotov5.ResourceIdentitySchema{
IdentityAttributes: []*tfprotov5.ResourceIdentitySchemaAttribute{
{
Name: "list_of_bools",
Type: tftypes.List{ElementType: tftypes.Bool},
RequiredForImport: true,
},
},
},
expected: &identityschema.Schema{
Attributes: map[string]identityschema.Attribute{
"list_of_bools": identityschema.ListAttribute{
ElementType: basetypes.BoolType{},
RequiredForImport: true,
},
},
},
},
"map-error": {
input: &tfprotov5.ResourceIdentitySchema{
IdentityAttributes: []*tfprotov5.ResourceIdentitySchemaAttribute{
{
Name: "map_of_strings",
Type: tftypes.Map{ElementType: tftypes.String},
OptionalForImport: true,
},
},
},
expectedErr: `no supported identity attribute for "map_of_strings", type: tftypes.Map`,
},
"set-error": {
input: &tfprotov5.ResourceIdentitySchema{
IdentityAttributes: []*tfprotov5.ResourceIdentitySchemaAttribute{
{
Name: "set_of_strings",
Type: tftypes.Set{ElementType: tftypes.String},
OptionalForImport: true,
},
},
},
expectedErr: `no supported identity attribute for "set_of_strings", type: tftypes.Set`,
},
}

for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
t.Parallel()

got, err := fromproto5.IdentitySchema(context.Background(), tc.input)
if err != nil {
if tc.expectedErr == "" {
t.Errorf("Unexpected error: %s", err)
return
}
if err.Error() != tc.expectedErr {
t.Errorf("Expected error to be %q, got %q", tc.expectedErr, err.Error())
return
}
// got expected error
return
}
if tc.expectedErr != "" {
t.Errorf("Expected error to be %q, got nil", tc.expectedErr)
return
}
if diff := cmp.Diff(got, tc.expected); diff != "" {
t.Errorf("Unexpected diff (+wanted, -got): %s", diff)
return
}
})
}
}
Loading
Loading