From 9ec6758ee4037f810af7338265a8132430d0f77c Mon Sep 17 00:00:00 2001 From: Austin Valle Date: Wed, 9 Jul 2025 13:54:22 -0400 Subject: [PATCH 1/8] go mod --- go.mod | 2 +- go.sum | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index 4479b2d26..3996d6843 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ toolchain go1.23.7 require ( github.com/google/go-cmp v0.7.0 - github.com/hashicorp/terraform-plugin-go v0.29.0-alpha.1 + github.com/hashicorp/terraform-plugin-go v0.29.0-alpha.1.0.20250709165734-a8477a15f806 github.com/hashicorp/terraform-plugin-log v0.9.0 ) diff --git a/go.sum b/go.sum index 5af35aa4f..58d2914b5 100644 --- a/go.sum +++ b/go.sum @@ -21,10 +21,8 @@ github.com/hashicorp/go-plugin v1.6.3 h1:xgHB+ZUSYeuJi96WtxEjzi23uh7YQpznjGh0U0U github.com/hashicorp/go-plugin v1.6.3/go.mod h1:MRobyh+Wc/nYy1V4KAXUiYfzxoYhs7V1mlH1Z7iY2h0= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/terraform-plugin-go v0.28.1-0.20250616135123-a19df43120ea h1:U9EAAeQtszGlR7mDS7rY77B/a4/XiMDB8HfAtqLAuAQ= -github.com/hashicorp/terraform-plugin-go v0.28.1-0.20250616135123-a19df43120ea/go.mod h1:hL//wLEfYo0YVt0TC/VLzia/ADQQto3HEm4/jX2gkdY= -github.com/hashicorp/terraform-plugin-go v0.29.0-alpha.1 h1:ZId6oWG8VTKhz207quE/Xh8a3HuoLtM/QkcSSypekIQ= -github.com/hashicorp/terraform-plugin-go v0.29.0-alpha.1/go.mod h1:hL//wLEfYo0YVt0TC/VLzia/ADQQto3HEm4/jX2gkdY= +github.com/hashicorp/terraform-plugin-go v0.29.0-alpha.1.0.20250709165734-a8477a15f806 h1:i3kA1sT/Fk8Ex+VVKdjf9sFOPwS7w3Q73pfbnxKwdjg= +github.com/hashicorp/terraform-plugin-go v0.29.0-alpha.1.0.20250709165734-a8477a15f806/go.mod h1:hL//wLEfYo0YVt0TC/VLzia/ADQQto3HEm4/jX2gkdY= github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow= github.com/hashicorp/terraform-registry-address v0.3.0 h1:HMpK3nqaGFPS9VmgRXrJL/dzHNdheGVKk5k7VlFxzCo= From 5eeac4cfb90b83dafc43157f797094e87b326100 Mon Sep 17 00:00:00 2001 From: Austin Valle Date: Wed, 9 Jul 2025 13:54:34 -0400 Subject: [PATCH 2/8] generate RPC methods --- internal/proto5server/server_invokeaction.go | 16 ++++++++++++++++ internal/proto5server/server_planaction.go | 16 ++++++++++++++++ internal/proto6server/server_invokeaction.go | 16 ++++++++++++++++ internal/proto6server/server_planaction.go | 16 ++++++++++++++++ 4 files changed, 64 insertions(+) create mode 100644 internal/proto5server/server_invokeaction.go create mode 100644 internal/proto5server/server_planaction.go create mode 100644 internal/proto6server/server_invokeaction.go create mode 100644 internal/proto6server/server_planaction.go diff --git a/internal/proto5server/server_invokeaction.go b/internal/proto5server/server_invokeaction.go new file mode 100644 index 000000000..90534898c --- /dev/null +++ b/internal/proto5server/server_invokeaction.go @@ -0,0 +1,16 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package proto5server + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-go/tfprotov5" +) + +// InvokeAction satisfies the tfprotov5.ProviderServer interface. +func (s *Server) InvokeAction(ctx context.Context, proto5Req *tfprotov5.InvokeActionRequest) (*tfprotov5.InvokeActionServerStream, error) { + // TODO:Actions: Implement + panic("unimplemented") +} diff --git a/internal/proto5server/server_planaction.go b/internal/proto5server/server_planaction.go new file mode 100644 index 000000000..d8a2adbd3 --- /dev/null +++ b/internal/proto5server/server_planaction.go @@ -0,0 +1,16 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package proto5server + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-go/tfprotov5" +) + +// PlanAction satisfies the tfprotov5.ProviderServer interface. +func (s *Server) PlanAction(ctx context.Context, proto5Req *tfprotov5.PlanActionRequest) (*tfprotov5.PlanActionResponse, error) { + // TODO:Actions: Implement + panic("unimplemented") +} diff --git a/internal/proto6server/server_invokeaction.go b/internal/proto6server/server_invokeaction.go new file mode 100644 index 000000000..80b80b6b8 --- /dev/null +++ b/internal/proto6server/server_invokeaction.go @@ -0,0 +1,16 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package proto6server + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-go/tfprotov6" +) + +// InvokeAction satisfies the tfprotov6.ProviderServer interface. +func (s *Server) InvokeAction(ctx context.Context, proto6Req *tfprotov6.InvokeActionRequest) (*tfprotov6.InvokeActionServerStream, error) { + // TODO:Actions: Implement + panic("unimplemented") +} diff --git a/internal/proto6server/server_planaction.go b/internal/proto6server/server_planaction.go new file mode 100644 index 000000000..42680d36a --- /dev/null +++ b/internal/proto6server/server_planaction.go @@ -0,0 +1,16 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package proto6server + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-go/tfprotov6" +) + +// PlanAction satisfies the tfprotov6.ProviderServer interface. +func (s *Server) PlanAction(ctx context.Context, proto6Req *tfprotov6.PlanActionRequest) (*tfprotov6.PlanActionResponse, error) { + // TODO:Actions: Implement + panic("unimplemented") +} From bf10880d2087259b3efb4aaf92cd783831fdf33a Mon Sep 17 00:00:00 2001 From: Austin Valle Date: Wed, 9 Jul 2025 18:02:09 -0400 Subject: [PATCH 3/8] protov5 and fwserver impl --- action/action.go | 16 ++ action/doc.go | 5 + action/metadata.go | 24 +++ action/schema.go | 28 ++++ internal/fromproto5/invokeaction.go | 52 +++++++ internal/fromproto5/invokeaction_test.go | 6 + internal/fromproto5/planaction.go | 52 +++++++ internal/fromproto5/planaction_test.go | 6 + internal/fwserver/server.go | 24 +++ internal/fwserver/server_actions.go | 142 ++++++++++++++++++ internal/fwserver/server_invokeaction.go | 32 ++++ internal/fwserver/server_invokeaction_test.go | 6 + internal/fwserver/server_planaction.go | 32 ++++ internal/fwserver/server_planaction_test.go | 6 + internal/logging/keys.go | 3 + internal/proto5server/server_invokeaction.go | 68 ++++++++- .../proto5server/server_invokeaction_test.go | 6 + internal/proto5server/server_planaction.go | 38 ++++- .../proto5server/server_planaction_test.go | 6 + internal/toproto5/planaction.go | 27 ++++ internal/toproto5/planaction_test.go | 6 + provider/provider.go | 16 ++ 22 files changed, 597 insertions(+), 4 deletions(-) create mode 100644 action/action.go create mode 100644 action/doc.go create mode 100644 action/metadata.go create mode 100644 action/schema.go create mode 100644 internal/fromproto5/invokeaction.go create mode 100644 internal/fromproto5/invokeaction_test.go create mode 100644 internal/fromproto5/planaction.go create mode 100644 internal/fromproto5/planaction_test.go create mode 100644 internal/fwserver/server_actions.go create mode 100644 internal/fwserver/server_invokeaction.go create mode 100644 internal/fwserver/server_invokeaction_test.go create mode 100644 internal/fwserver/server_planaction.go create mode 100644 internal/fwserver/server_planaction_test.go create mode 100644 internal/proto5server/server_invokeaction_test.go create mode 100644 internal/proto5server/server_planaction_test.go create mode 100644 internal/toproto5/planaction.go create mode 100644 internal/toproto5/planaction_test.go diff --git a/action/action.go b/action/action.go new file mode 100644 index 000000000..f172651f9 --- /dev/null +++ b/action/action.go @@ -0,0 +1,16 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package action + +import "context" + +type Action interface { + // Schema should return the schema for this action. + Schema(context.Context, SchemaRequest, *SchemaResponse) + + // Metadata should return the full name of the action, such as examplecloud_do_thing. + Metadata(context.Context, MetadataRequest, *MetadataResponse) + + // TODO:Actions: Eventual landing place for all required methods to implement for an action +} diff --git a/action/doc.go b/action/doc.go new file mode 100644 index 000000000..351e8b352 --- /dev/null +++ b/action/doc.go @@ -0,0 +1,5 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// TODO:Actions: Eventual package docs for actions +package action diff --git a/action/metadata.go b/action/metadata.go new file mode 100644 index 000000000..46b4c3366 --- /dev/null +++ b/action/metadata.go @@ -0,0 +1,24 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package action + +// MetadataRequest represents a request for the Action to return metadata, +// such as its type name. An instance of this request struct is supplied as +// an argument to the Action type Metadata method. +type MetadataRequest struct { + // ProviderTypeName is the string returned from + // [provider.MetadataResponse.TypeName], if the Provider type implements + // the Metadata method. This string should prefix the Action type name + // with an underscore in the response. + ProviderTypeName string +} + +// MetadataResponse represents a response to a MetadataRequest. An +// instance of this response struct is supplied as an argument to the +// Action type Metadata method. +type MetadataResponse struct { + // TypeName should be the full action type, including the provider + // type prefix and an underscore. For example, examplecloud_thing. + TypeName string +} diff --git a/action/schema.go b/action/schema.go new file mode 100644 index 000000000..9eb52bf7b --- /dev/null +++ b/action/schema.go @@ -0,0 +1,28 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package action + +import ( + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" +) + +// SchemaRequest represents a request for the Action to return its schema. +// An instance of this request struct is supplied as an argument to the +// Action type Schema method. +type SchemaRequest struct{} + +// SchemaResponse represents a response to a SchemaRequest. An instance of this +// response struct is supplied as an argument to the Action type Schema +// method. +type SchemaResponse struct { + // TODO:Actions: This will eventually be replaced by an interface defined in + // an "actions/schema" package. Schema implementations that will fulfill this + // interface will be unlinked, linked, or lifecycle. (also defined in the "actions/schema" package) + Schema fwschema.Schema + + // Diagnostics report errors or warnings related to retrieving the action schema. + // An empty slice indicates success, with no warnings or errors generated. + Diagnostics diag.Diagnostics +} diff --git a/internal/fromproto5/invokeaction.go b/internal/fromproto5/invokeaction.go new file mode 100644 index 000000000..6290147d3 --- /dev/null +++ b/internal/fromproto5/invokeaction.go @@ -0,0 +1,52 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package fromproto5 + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-go/tfprotov5" + + "github.com/hashicorp/terraform-plugin-framework/action" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" + "github.com/hashicorp/terraform-plugin-framework/internal/fwserver" +) + +// InvokeActionRequest returns the *fwserver.InvokeActionRequest equivalent of a *tfprotov5.InvokeActionRequest. +func InvokeActionRequest(ctx context.Context, proto5 *tfprotov5.InvokeActionRequest, reqAction action.Action, actionSchema fwschema.Schema) (*fwserver.InvokeActionRequest, diag.Diagnostics) { + if proto5 == nil { + return nil, nil + } + + var diags diag.Diagnostics + + // Panic prevention here to simplify the calling implementations. + // This should not happen, but just in case. + if actionSchema == nil { + diags.AddError( + "Missing Action Schema", + "An unexpected error was encountered when handling the request. "+ + "This is always an issue in terraform-plugin-framework used to implement the provider and should be reported to the provider developers.\n\n"+ + "Please report this to the provider developer:\n\n"+ + "Missing schema.", + ) + + return nil, diags + } + + fw := &fwserver.InvokeActionRequest{ + ActionSchema: actionSchema, + } + + config, configDiags := Config(ctx, proto5.Config, actionSchema) + + diags.Append(configDiags...) + + fw.Config = config + + // TODO:Actions: Here we need to retrieve linked resource data + + return fw, diags +} diff --git a/internal/fromproto5/invokeaction_test.go b/internal/fromproto5/invokeaction_test.go new file mode 100644 index 000000000..c5b20a804 --- /dev/null +++ b/internal/fromproto5/invokeaction_test.go @@ -0,0 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package fromproto5_test + +// TODO:Actions: Add unit tests once this mapping logic is complete diff --git a/internal/fromproto5/planaction.go b/internal/fromproto5/planaction.go new file mode 100644 index 000000000..bb71e5815 --- /dev/null +++ b/internal/fromproto5/planaction.go @@ -0,0 +1,52 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package fromproto5 + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-go/tfprotov5" + + "github.com/hashicorp/terraform-plugin-framework/action" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" + "github.com/hashicorp/terraform-plugin-framework/internal/fwserver" +) + +// PlanActionRequest returns the *fwserver.PlanActionRequest equivalent of a *tfprotov5.PlanActionRequest. +func PlanActionRequest(ctx context.Context, proto5 *tfprotov5.PlanActionRequest, reqAction action.Action, actionSchema fwschema.Schema) (*fwserver.PlanActionRequest, diag.Diagnostics) { + if proto5 == nil { + return nil, nil + } + + var diags diag.Diagnostics + + // Panic prevention here to simplify the calling implementations. + // This should not happen, but just in case. + if actionSchema == nil { + diags.AddError( + "Missing Action Schema", + "An unexpected error was encountered when handling the request. "+ + "This is always an issue in terraform-plugin-framework used to implement the provider and should be reported to the provider developers.\n\n"+ + "Please report this to the provider developer:\n\n"+ + "Missing schema.", + ) + + return nil, diags + } + + fw := &fwserver.PlanActionRequest{ + ActionSchema: actionSchema, + } + + config, configDiags := Config(ctx, proto5.Config, actionSchema) + + diags.Append(configDiags...) + + fw.Config = config + + // TODO:Actions: Here we need to retrieve client capabilities and linked resource data + + return fw, diags +} diff --git a/internal/fromproto5/planaction_test.go b/internal/fromproto5/planaction_test.go new file mode 100644 index 000000000..c5b20a804 --- /dev/null +++ b/internal/fromproto5/planaction_test.go @@ -0,0 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package fromproto5_test + +// TODO:Actions: Add unit tests once this mapping logic is complete diff --git a/internal/fwserver/server.go b/internal/fwserver/server.go index f178b21aa..3fef10f28 100644 --- a/internal/fwserver/server.go +++ b/internal/fwserver/server.go @@ -8,6 +8,7 @@ import ( "fmt" "sync" + "github.com/hashicorp/terraform-plugin-framework/action" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/ephemeral" @@ -40,6 +41,29 @@ type Server struct { // to [ephemeral.ConfigureRequest.ProviderData]. EphemeralResourceConfigureData any + // actionSchemas is the cached Action Schemas for RPCs that need to + // convert configuration data from the protocol. If not found, it will be + // fetched from the Action.Schema() method. + actionSchemas map[string]fwschema.Schema + + // actionSchemasMutex is a mutex to protect concurrent actionSchemas + // access from race conditions. + actionSchemasMutex sync.RWMutex + + // actionFuncs is the cached Action functions for RPCs that need to + // access actions. If not found, it will be fetched from the + // Provider.Actions() method. + actionFuncs map[string]func() action.Action + + // actionFuncsDiags is the cached Diagnostics obtained while populating + // actionFuncs. This is to ensure any warnings or errors are also + // returned appropriately when fetching actionFuncs. + actionFuncsDiags diag.Diagnostics + + // actionFuncsMutex is a mutex to protect concurrent actionFuncs + // access from race conditions. + actionFuncsMutex sync.Mutex + // dataSourceSchemas is the cached DataSource Schemas for RPCs that need to // convert configuration data from the protocol. If not found, it will be // fetched from the DataSourceType.GetSchema() method. diff --git a/internal/fwserver/server_actions.go b/internal/fwserver/server_actions.go new file mode 100644 index 000000000..73f2b68a8 --- /dev/null +++ b/internal/fwserver/server_actions.go @@ -0,0 +1,142 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package fwserver + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/action" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" + "github.com/hashicorp/terraform-plugin-framework/internal/logging" + "github.com/hashicorp/terraform-plugin-framework/provider" +) + +// Action returns the Action for a given action type. +func (s *Server) Action(ctx context.Context, actionType string) (action.Action, diag.Diagnostics) { + actionFuncs, diags := s.ActionFuncs(ctx) + + actionFunc, ok := actionFuncs[actionType] + + if !ok { + diags.AddError( + "Action Type Not Found", + fmt.Sprintf("No action type named %q was found in the provider.", actionType), + ) + + return nil, diags + } + + return actionFunc(), diags +} + +// ActionFuncs returns a map of Action functions. The results are cached +// on first use. +func (s *Server) ActionFuncs(ctx context.Context) (map[string]func() action.Action, diag.Diagnostics) { + logging.FrameworkTrace(ctx, "Checking ActionFuncs lock") + s.actionFuncsMutex.Lock() + defer s.actionFuncsMutex.Unlock() + + if s.actionFuncs != nil { + return s.actionFuncs, s.actionFuncsDiags + } + + providerTypeName := s.ProviderTypeName(ctx) + s.actionFuncs = make(map[string]func() action.Action) + + provider, ok := s.Provider.(provider.ProviderWithActions) + if !ok { + // Only action specific RPCs should return diagnostics about the + // provider not implementing actions or missing actions. + return s.actionFuncs, s.actionFuncsDiags + } + + logging.FrameworkTrace(ctx, "Calling provider defined Provider Actions") + actionFuncsSlice := provider.Actions(ctx) + logging.FrameworkTrace(ctx, "Called provider defined Provider Actions") + + for _, actionFunc := range actionFuncsSlice { + actionImpl := actionFunc() + + actionTypeReq := action.MetadataRequest{ + ProviderTypeName: providerTypeName, + } + actionTypeResp := action.MetadataResponse{} + + actionImpl.Metadata(ctx, actionTypeReq, &actionTypeResp) + + if actionTypeResp.TypeName == "" { + s.actionFuncsDiags.AddError( + "Action Type Missing", + fmt.Sprintf("The %T Action returned an empty string from the Metadata method. ", actionImpl)+ + "This is always an issue with the provider and should be reported to the provider developers.", + ) + continue + } + + logging.FrameworkTrace(ctx, "Found action", map[string]interface{}{logging.KeyActionType: actionTypeResp.TypeName}) + + if _, ok := s.actionFuncs[actionTypeResp.TypeName]; ok { + s.actionFuncsDiags.AddError( + "Duplicate Action Defined", + fmt.Sprintf("The %s action type was returned for multiple actions. ", actionTypeResp.TypeName)+ + "Action types must be unique. "+ + "This is always an issue with the provider and should be reported to the provider developers.", + ) + continue + } + + s.actionFuncs[actionTypeResp.TypeName] = actionFunc + } + + return s.actionFuncs, s.actionFuncsDiags +} + +// ActionSchema returns the Action Schema for the given type name and +// caches the result for later Action operations. +func (s *Server) ActionSchema(ctx context.Context, actionType string) (fwschema.Schema, diag.Diagnostics) { + s.actionSchemasMutex.RLock() + actionSchema, ok := s.actionSchemas[actionType] + s.actionSchemasMutex.RUnlock() + + if ok { + return actionSchema, nil + } + + var diags diag.Diagnostics + + actionImpl, actionDiags := s.Action(ctx, actionType) + + diags.Append(actionDiags...) + + if diags.HasError() { + return nil, diags + } + + schemaReq := action.SchemaRequest{} + schemaResp := action.SchemaResponse{} + + logging.FrameworkTrace(ctx, "Calling provider defined Action Schema method", map[string]interface{}{logging.KeyActionType: actionType}) + actionImpl.Schema(ctx, schemaReq, &schemaResp) + logging.FrameworkTrace(ctx, "Called provider defined Action Schema method", map[string]interface{}{logging.KeyActionType: actionType}) + + diags.Append(schemaResp.Diagnostics...) + + if diags.HasError() { + return schemaResp.Schema, diags + } + + s.actionSchemasMutex.Lock() + + if s.actionSchemas == nil { + s.actionSchemas = make(map[string]fwschema.Schema) + } + + s.actionSchemas[actionType] = schemaResp.Schema + + s.actionSchemasMutex.Unlock() + + return schemaResp.Schema, diags +} diff --git a/internal/fwserver/server_invokeaction.go b/internal/fwserver/server_invokeaction.go new file mode 100644 index 000000000..200a5b811 --- /dev/null +++ b/internal/fwserver/server_invokeaction.go @@ -0,0 +1,32 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package fwserver + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" +) + +// InvokeActionRequest is the framework server request for the InvokeAction RPC. +type InvokeActionRequest struct { + ActionSchema fwschema.Schema + Config *tfsdk.Config +} + +// InvokeActionEventsStream is the framework server stream for the InvokeAction RPC. +type InvokeActionResponse struct { + Diagnostics diag.Diagnostics +} + +// InvokeAction implements the framework server InvokeAction RPC. +func (s *Server) InvokeAction(ctx context.Context, req *InvokeActionRequest, resp *InvokeActionResponse) { + // TODO:Actions: Implementation coming soon... + resp.Diagnostics.AddError( + "InvokeAction Not Implemented", + "InvokeAction has not yet been implemented in terraform-plugin-framework.", + ) +} diff --git a/internal/fwserver/server_invokeaction_test.go b/internal/fwserver/server_invokeaction_test.go new file mode 100644 index 000000000..5879cddb3 --- /dev/null +++ b/internal/fwserver/server_invokeaction_test.go @@ -0,0 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package fwserver_test + +// TODO:Actions: Add unit tests once InvokeAction is implemented diff --git a/internal/fwserver/server_planaction.go b/internal/fwserver/server_planaction.go new file mode 100644 index 000000000..1a5bc192f --- /dev/null +++ b/internal/fwserver/server_planaction.go @@ -0,0 +1,32 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package fwserver + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" +) + +// PlanActionRequest is the framework server request for the PlanAction RPC. +type PlanActionRequest struct { + ActionSchema fwschema.Schema + Config *tfsdk.Config +} + +// PlanActionResponse is the framework server response for the PlanAction RPC. +type PlanActionResponse struct { + Diagnostics diag.Diagnostics +} + +// PlanAction implements the framework server PlanAction RPC. +func (s *Server) PlanAction(ctx context.Context, req *PlanActionRequest, resp *PlanActionResponse) { + // TODO:Actions: Implementation coming soon... + resp.Diagnostics.AddError( + "PlanAction Not Implemented", + "PlanAction has not yet been implemented in terraform-plugin-framework.", + ) +} diff --git a/internal/fwserver/server_planaction_test.go b/internal/fwserver/server_planaction_test.go new file mode 100644 index 000000000..1ecb5c4f3 --- /dev/null +++ b/internal/fwserver/server_planaction_test.go @@ -0,0 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package fwserver_test + +// TODO:Actions: Add unit tests once PlanAction is implemented diff --git a/internal/logging/keys.go b/internal/logging/keys.go index c9d2dc9a9..a18586320 100644 --- a/internal/logging/keys.go +++ b/internal/logging/keys.go @@ -15,6 +15,9 @@ const ( // as parent.0.child in this project. KeyAttributePath = "tf_attribute_path" + // The type of action being operated on, such as "examplecloud_do_thing" + KeyActionType = "tf_action_type" + // The type of data source being operated on, such as "archive_file" KeyDataSourceType = "tf_data_source_type" diff --git a/internal/proto5server/server_invokeaction.go b/internal/proto5server/server_invokeaction.go index 90534898c..e7e2d6fd6 100644 --- a/internal/proto5server/server_invokeaction.go +++ b/internal/proto5server/server_invokeaction.go @@ -6,11 +6,75 @@ package proto5server import ( "context" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/internal/fromproto5" + "github.com/hashicorp/terraform-plugin-framework/internal/fwserver" + "github.com/hashicorp/terraform-plugin-framework/internal/logging" + "github.com/hashicorp/terraform-plugin-framework/internal/toproto5" "github.com/hashicorp/terraform-plugin-go/tfprotov5" ) +// invokeActionErrorDiagnostics returns a value suitable for +// [InvokeActionServerStream.Events]. It yields a single result that contains +// the given error diagnostics. +func invokeActionErrorDiagnostics(ctx context.Context, diags diag.Diagnostics) (*tfprotov5.InvokeActionServerStream, error) { + return &tfprotov5.InvokeActionServerStream{ + Events: func(push func(tfprotov5.InvokeActionEvent) bool) { + push(tfprotov5.InvokeActionEvent{ + Type: tfprotov5.CompletedInvokeActionEventType{ + Diagnostics: toproto5.Diagnostics(ctx, diags), + }, + }) + }, + }, nil +} + // InvokeAction satisfies the tfprotov5.ProviderServer interface. func (s *Server) InvokeAction(ctx context.Context, proto5Req *tfprotov5.InvokeActionRequest) (*tfprotov5.InvokeActionServerStream, error) { - // TODO:Actions: Implement - panic("unimplemented") + ctx = s.registerContext(ctx) + ctx = logging.InitContext(ctx) + + fwResp := &fwserver.InvokeActionResponse{} + + action, diags := s.FrameworkServer.Action(ctx, proto5Req.ActionType) + + fwResp.Diagnostics.Append(diags...) + + if fwResp.Diagnostics.HasError() { + return invokeActionErrorDiagnostics(ctx, fwResp.Diagnostics) + } + + actionSchema, diags := s.FrameworkServer.ActionSchema(ctx, proto5Req.ActionType) + + fwResp.Diagnostics.Append(diags...) + + if fwResp.Diagnostics.HasError() { + return invokeActionErrorDiagnostics(ctx, fwResp.Diagnostics) + } + + fwReq, diags := fromproto5.InvokeActionRequest(ctx, proto5Req, action, actionSchema) + + fwResp.Diagnostics.Append(diags...) + + if fwResp.Diagnostics.HasError() { + return invokeActionErrorDiagnostics(ctx, fwResp.Diagnostics) + } + + s.FrameworkServer.InvokeAction(ctx, fwReq, fwResp) + + // TODO:Actions: This is a stub implementation, so we aren't currently exposing any streaming mechanism to the developer. + // That will eventually need to change to send progress events back to Terraform. + // + // This logic will likely need to be moved over to the "toproto" package as well. + protoStream := &tfprotov5.InvokeActionServerStream{ + Events: func(push func(tfprotov5.InvokeActionEvent) bool) { + push(tfprotov5.InvokeActionEvent{ + Type: tfprotov5.CompletedInvokeActionEventType{ + Diagnostics: toproto5.Diagnostics(ctx, fwResp.Diagnostics), + }, + }) + }, + } + + return protoStream, nil } diff --git a/internal/proto5server/server_invokeaction_test.go b/internal/proto5server/server_invokeaction_test.go new file mode 100644 index 000000000..4cc50c03a --- /dev/null +++ b/internal/proto5server/server_invokeaction_test.go @@ -0,0 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package proto5server_test + +// TODO:Actions: Add unit tests once InvokeAction is implemented diff --git a/internal/proto5server/server_planaction.go b/internal/proto5server/server_planaction.go index d8a2adbd3..39a31ff4b 100644 --- a/internal/proto5server/server_planaction.go +++ b/internal/proto5server/server_planaction.go @@ -6,11 +6,45 @@ package proto5server import ( "context" + "github.com/hashicorp/terraform-plugin-framework/internal/fromproto5" + "github.com/hashicorp/terraform-plugin-framework/internal/fwserver" + "github.com/hashicorp/terraform-plugin-framework/internal/logging" + "github.com/hashicorp/terraform-plugin-framework/internal/toproto5" "github.com/hashicorp/terraform-plugin-go/tfprotov5" ) // PlanAction satisfies the tfprotov5.ProviderServer interface. func (s *Server) PlanAction(ctx context.Context, proto5Req *tfprotov5.PlanActionRequest) (*tfprotov5.PlanActionResponse, error) { - // TODO:Actions: Implement - panic("unimplemented") + ctx = s.registerContext(ctx) + ctx = logging.InitContext(ctx) + + fwResp := &fwserver.PlanActionResponse{} + + action, diags := s.FrameworkServer.Action(ctx, proto5Req.ActionType) + + fwResp.Diagnostics.Append(diags...) + + if fwResp.Diagnostics.HasError() { + return toproto5.PlanActionResponse(ctx, fwResp), nil + } + + actionSchema, diags := s.FrameworkServer.ActionSchema(ctx, proto5Req.ActionType) + + fwResp.Diagnostics.Append(diags...) + + if fwResp.Diagnostics.HasError() { + return toproto5.PlanActionResponse(ctx, fwResp), nil + } + + fwReq, diags := fromproto5.PlanActionRequest(ctx, proto5Req, action, actionSchema) + + fwResp.Diagnostics.Append(diags...) + + if fwResp.Diagnostics.HasError() { + return toproto5.PlanActionResponse(ctx, fwResp), nil + } + + s.FrameworkServer.PlanAction(ctx, fwReq, fwResp) + + return toproto5.PlanActionResponse(ctx, fwResp), nil } diff --git a/internal/proto5server/server_planaction_test.go b/internal/proto5server/server_planaction_test.go new file mode 100644 index 000000000..ccf86051e --- /dev/null +++ b/internal/proto5server/server_planaction_test.go @@ -0,0 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package proto5server_test + +// TODO:Actions: Add unit tests once PlanAction is implemented diff --git a/internal/toproto5/planaction.go b/internal/toproto5/planaction.go new file mode 100644 index 000000000..b55fd4557 --- /dev/null +++ b/internal/toproto5/planaction.go @@ -0,0 +1,27 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package toproto5 + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-go/tfprotov5" + + "github.com/hashicorp/terraform-plugin-framework/internal/fwserver" +) + +// PlanActionResponse returns the *tfprotov5.PlanActionResponse equivalent of a *fwserver.PlanActionResponse. +func PlanActionResponse(ctx context.Context, fw *fwserver.PlanActionResponse) *tfprotov5.PlanActionResponse { + if fw == nil { + return nil + } + + proto5 := &tfprotov5.PlanActionResponse{ + Diagnostics: Diagnostics(ctx, fw.Diagnostics), + } + + // TODO:Actions: Here we need to set deferred and linked resource data + + return proto5 +} diff --git a/internal/toproto5/planaction_test.go b/internal/toproto5/planaction_test.go new file mode 100644 index 000000000..41e665462 --- /dev/null +++ b/internal/toproto5/planaction_test.go @@ -0,0 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package toproto5_test + +// TODO:Actions: Add unit tests once this mapping logic is complete diff --git a/provider/provider.go b/provider/provider.go index a7dc583f6..f32e2773d 100644 --- a/provider/provider.go +++ b/provider/provider.go @@ -6,6 +6,7 @@ package provider import ( "context" + "github.com/hashicorp/terraform-plugin-framework/action" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/ephemeral" "github.com/hashicorp/terraform-plugin-framework/function" @@ -134,6 +135,21 @@ type ProviderWithListResources interface { ListResources(context.Context) []func() list.ListResource } +// ProviderWithActions is an interface type that extends Provider to +// include actions for usage in practitioner configurations. +// +// TODO:Actions: State which Terraform version will support actions +type ProviderWithActions interface { + Provider + + // Actions returns a slice of functions to instantiate each Action + // implementation. + // + // The action type is determined by the Action implementing + // the Metadata method. All action types must have unique names. + Actions(context.Context) []func() action.Action +} + // ProviderWithValidateConfig is an interface type that extends Provider to include imperative validation. // // Declaring validation using this methodology simplifies one-off From 3c21a45de28a92992d3596921a01a42ae7983514 Mon Sep 17 00:00:00 2001 From: Austin Valle Date: Wed, 9 Jul 2025 18:07:03 -0400 Subject: [PATCH 4/8] protov6 copy --- internal/fromproto6/invokeaction.go | 52 ++++++++++++++ internal/fromproto6/invokeaction_test.go | 6 ++ internal/fromproto6/planaction.go | 52 ++++++++++++++ internal/fromproto6/planaction_test.go | 6 ++ internal/proto6server/server_invokeaction.go | 68 ++++++++++++++++++- .../proto6server/server_invokeaction_test.go | 6 ++ internal/proto6server/server_planaction.go | 38 ++++++++++- .../proto6server/server_planaction_test.go | 6 ++ internal/toproto6/planaction.go | 27 ++++++++ internal/toproto6/planaction_test.go | 6 ++ 10 files changed, 263 insertions(+), 4 deletions(-) create mode 100644 internal/fromproto6/invokeaction.go create mode 100644 internal/fromproto6/invokeaction_test.go create mode 100644 internal/fromproto6/planaction.go create mode 100644 internal/fromproto6/planaction_test.go create mode 100644 internal/proto6server/server_invokeaction_test.go create mode 100644 internal/proto6server/server_planaction_test.go create mode 100644 internal/toproto6/planaction.go create mode 100644 internal/toproto6/planaction_test.go diff --git a/internal/fromproto6/invokeaction.go b/internal/fromproto6/invokeaction.go new file mode 100644 index 000000000..270a2bacb --- /dev/null +++ b/internal/fromproto6/invokeaction.go @@ -0,0 +1,52 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package fromproto6 + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + + "github.com/hashicorp/terraform-plugin-framework/action" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" + "github.com/hashicorp/terraform-plugin-framework/internal/fwserver" +) + +// InvokeActionRequest returns the *fwserver.InvokeActionRequest equivalent of a *tfprotov6.InvokeActionRequest. +func InvokeActionRequest(ctx context.Context, proto6 *tfprotov6.InvokeActionRequest, reqAction action.Action, actionSchema fwschema.Schema) (*fwserver.InvokeActionRequest, diag.Diagnostics) { + if proto6 == nil { + return nil, nil + } + + var diags diag.Diagnostics + + // Panic prevention here to simplify the calling implementations. + // This should not happen, but just in case. + if actionSchema == nil { + diags.AddError( + "Missing Action Schema", + "An unexpected error was encountered when handling the request. "+ + "This is always an issue in terraform-plugin-framework used to implement the provider and should be reported to the provider developers.\n\n"+ + "Please report this to the provider developer:\n\n"+ + "Missing schema.", + ) + + return nil, diags + } + + fw := &fwserver.InvokeActionRequest{ + ActionSchema: actionSchema, + } + + config, configDiags := Config(ctx, proto6.Config, actionSchema) + + diags.Append(configDiags...) + + fw.Config = config + + // TODO:Actions: Here we need to retrieve linked resource data + + return fw, diags +} diff --git a/internal/fromproto6/invokeaction_test.go b/internal/fromproto6/invokeaction_test.go new file mode 100644 index 000000000..5c9fa3702 --- /dev/null +++ b/internal/fromproto6/invokeaction_test.go @@ -0,0 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package fromproto6_test + +// TODO:Actions: Add unit tests once this mapping logic is complete diff --git a/internal/fromproto6/planaction.go b/internal/fromproto6/planaction.go new file mode 100644 index 000000000..7715037c9 --- /dev/null +++ b/internal/fromproto6/planaction.go @@ -0,0 +1,52 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package fromproto6 + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + + "github.com/hashicorp/terraform-plugin-framework/action" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" + "github.com/hashicorp/terraform-plugin-framework/internal/fwserver" +) + +// PlanActionRequest returns the *fwserver.PlanActionRequest equivalent of a *tfprotov6.PlanActionRequest. +func PlanActionRequest(ctx context.Context, proto6 *tfprotov6.PlanActionRequest, reqAction action.Action, actionSchema fwschema.Schema) (*fwserver.PlanActionRequest, diag.Diagnostics) { + if proto6 == nil { + return nil, nil + } + + var diags diag.Diagnostics + + // Panic prevention here to simplify the calling implementations. + // This should not happen, but just in case. + if actionSchema == nil { + diags.AddError( + "Missing Action Schema", + "An unexpected error was encountered when handling the request. "+ + "This is always an issue in terraform-plugin-framework used to implement the provider and should be reported to the provider developers.\n\n"+ + "Please report this to the provider developer:\n\n"+ + "Missing schema.", + ) + + return nil, diags + } + + fw := &fwserver.PlanActionRequest{ + ActionSchema: actionSchema, + } + + config, configDiags := Config(ctx, proto6.Config, actionSchema) + + diags.Append(configDiags...) + + fw.Config = config + + // TODO:Actions: Here we need to retrieve client capabilities and linked resource data + + return fw, diags +} diff --git a/internal/fromproto6/planaction_test.go b/internal/fromproto6/planaction_test.go new file mode 100644 index 000000000..5c9fa3702 --- /dev/null +++ b/internal/fromproto6/planaction_test.go @@ -0,0 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package fromproto6_test + +// TODO:Actions: Add unit tests once this mapping logic is complete diff --git a/internal/proto6server/server_invokeaction.go b/internal/proto6server/server_invokeaction.go index 80b80b6b8..3a6d78cee 100644 --- a/internal/proto6server/server_invokeaction.go +++ b/internal/proto6server/server_invokeaction.go @@ -6,11 +6,75 @@ package proto6server import ( "context" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/internal/fromproto6" + "github.com/hashicorp/terraform-plugin-framework/internal/fwserver" + "github.com/hashicorp/terraform-plugin-framework/internal/logging" + "github.com/hashicorp/terraform-plugin-framework/internal/toproto6" "github.com/hashicorp/terraform-plugin-go/tfprotov6" ) +// invokeActionErrorDiagnostics returns a value suitable for +// [InvokeActionServerStream.Events]. It yields a single result that contains +// the given error diagnostics. +func invokeActionErrorDiagnostics(ctx context.Context, diags diag.Diagnostics) (*tfprotov6.InvokeActionServerStream, error) { + return &tfprotov6.InvokeActionServerStream{ + Events: func(push func(tfprotov6.InvokeActionEvent) bool) { + push(tfprotov6.InvokeActionEvent{ + Type: tfprotov6.CompletedInvokeActionEventType{ + Diagnostics: toproto6.Diagnostics(ctx, diags), + }, + }) + }, + }, nil +} + // InvokeAction satisfies the tfprotov6.ProviderServer interface. func (s *Server) InvokeAction(ctx context.Context, proto6Req *tfprotov6.InvokeActionRequest) (*tfprotov6.InvokeActionServerStream, error) { - // TODO:Actions: Implement - panic("unimplemented") + ctx = s.registerContext(ctx) + ctx = logging.InitContext(ctx) + + fwResp := &fwserver.InvokeActionResponse{} + + action, diags := s.FrameworkServer.Action(ctx, proto6Req.ActionType) + + fwResp.Diagnostics.Append(diags...) + + if fwResp.Diagnostics.HasError() { + return invokeActionErrorDiagnostics(ctx, fwResp.Diagnostics) + } + + actionSchema, diags := s.FrameworkServer.ActionSchema(ctx, proto6Req.ActionType) + + fwResp.Diagnostics.Append(diags...) + + if fwResp.Diagnostics.HasError() { + return invokeActionErrorDiagnostics(ctx, fwResp.Diagnostics) + } + + fwReq, diags := fromproto6.InvokeActionRequest(ctx, proto6Req, action, actionSchema) + + fwResp.Diagnostics.Append(diags...) + + if fwResp.Diagnostics.HasError() { + return invokeActionErrorDiagnostics(ctx, fwResp.Diagnostics) + } + + s.FrameworkServer.InvokeAction(ctx, fwReq, fwResp) + + // TODO:Actions: This is a stub implementation, so we aren't currently exposing any streaming mechanism to the developer. + // That will eventually need to change to send progress events back to Terraform. + // + // This logic will likely need to be moved over to the "toproto" package as well. + protoStream := &tfprotov6.InvokeActionServerStream{ + Events: func(push func(tfprotov6.InvokeActionEvent) bool) { + push(tfprotov6.InvokeActionEvent{ + Type: tfprotov6.CompletedInvokeActionEventType{ + Diagnostics: toproto6.Diagnostics(ctx, fwResp.Diagnostics), + }, + }) + }, + } + + return protoStream, nil } diff --git a/internal/proto6server/server_invokeaction_test.go b/internal/proto6server/server_invokeaction_test.go new file mode 100644 index 000000000..7bc3d2a68 --- /dev/null +++ b/internal/proto6server/server_invokeaction_test.go @@ -0,0 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package proto6server_test + +// TODO:Actions: Add unit tests once InvokeAction is implemented diff --git a/internal/proto6server/server_planaction.go b/internal/proto6server/server_planaction.go index 42680d36a..a92a28d63 100644 --- a/internal/proto6server/server_planaction.go +++ b/internal/proto6server/server_planaction.go @@ -6,11 +6,45 @@ package proto6server import ( "context" + "github.com/hashicorp/terraform-plugin-framework/internal/fromproto6" + "github.com/hashicorp/terraform-plugin-framework/internal/fwserver" + "github.com/hashicorp/terraform-plugin-framework/internal/logging" + "github.com/hashicorp/terraform-plugin-framework/internal/toproto6" "github.com/hashicorp/terraform-plugin-go/tfprotov6" ) // PlanAction satisfies the tfprotov6.ProviderServer interface. func (s *Server) PlanAction(ctx context.Context, proto6Req *tfprotov6.PlanActionRequest) (*tfprotov6.PlanActionResponse, error) { - // TODO:Actions: Implement - panic("unimplemented") + ctx = s.registerContext(ctx) + ctx = logging.InitContext(ctx) + + fwResp := &fwserver.PlanActionResponse{} + + action, diags := s.FrameworkServer.Action(ctx, proto6Req.ActionType) + + fwResp.Diagnostics.Append(diags...) + + if fwResp.Diagnostics.HasError() { + return toproto6.PlanActionResponse(ctx, fwResp), nil + } + + actionSchema, diags := s.FrameworkServer.ActionSchema(ctx, proto6Req.ActionType) + + fwResp.Diagnostics.Append(diags...) + + if fwResp.Diagnostics.HasError() { + return toproto6.PlanActionResponse(ctx, fwResp), nil + } + + fwReq, diags := fromproto6.PlanActionRequest(ctx, proto6Req, action, actionSchema) + + fwResp.Diagnostics.Append(diags...) + + if fwResp.Diagnostics.HasError() { + return toproto6.PlanActionResponse(ctx, fwResp), nil + } + + s.FrameworkServer.PlanAction(ctx, fwReq, fwResp) + + return toproto6.PlanActionResponse(ctx, fwResp), nil } diff --git a/internal/proto6server/server_planaction_test.go b/internal/proto6server/server_planaction_test.go new file mode 100644 index 000000000..0ff1b5e7c --- /dev/null +++ b/internal/proto6server/server_planaction_test.go @@ -0,0 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package proto6server_test + +// TODO:Actions: Add unit tests once PlanAction is implemented diff --git a/internal/toproto6/planaction.go b/internal/toproto6/planaction.go new file mode 100644 index 000000000..9d4f86ac8 --- /dev/null +++ b/internal/toproto6/planaction.go @@ -0,0 +1,27 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package toproto6 + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + + "github.com/hashicorp/terraform-plugin-framework/internal/fwserver" +) + +// PlanActionResponse returns the *tfprotov6.PlanActionResponse equivalent of a *fwserver.PlanActionResponse. +func PlanActionResponse(ctx context.Context, fw *fwserver.PlanActionResponse) *tfprotov6.PlanActionResponse { + if fw == nil { + return nil + } + + proto6 := &tfprotov6.PlanActionResponse{ + Diagnostics: Diagnostics(ctx, fw.Diagnostics), + } + + // TODO:Actions: Here we need to set deferred and linked resource data + + return proto6 +} diff --git a/internal/toproto6/planaction_test.go b/internal/toproto6/planaction_test.go new file mode 100644 index 000000000..29cade55e --- /dev/null +++ b/internal/toproto6/planaction_test.go @@ -0,0 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package toproto6_test + +// TODO:Actions: Add unit tests once this mapping logic is complete From 61eadc9fe7f8247b17d7e5d9f9c14ea01107c4a6 Mon Sep 17 00:00:00 2001 From: Austin Valle Date: Thu, 10 Jul 2025 10:40:30 -0400 Subject: [PATCH 5/8] add initial schema attributes and unlinked schema --- action/schema/attribute.go | 52 + action/schema/block.go | 43 + action/schema/bool_attribute.go | 170 +++ action/schema/bool_attribute_test.go | 437 ++++++ action/schema/list_attribute.go | 204 +++ action/schema/list_attribute_test.go | 522 +++++++ action/schema/nested_attribute.go | 14 + action/schema/nested_attribute_object.go | 65 + action/schema/nested_attribute_object_test.go | 233 +++ action/schema/nested_block_object.go | 77 + action/schema/nested_block_object_test.go | 317 +++++ action/schema/schema_type.go | 26 + action/schema/single_nested_attribute.go | 226 +++ action/schema/single_nested_attribute_test.go | 575 ++++++++ action/schema/single_nested_block.go | 191 +++ action/schema/single_nested_block_test.go | 433 ++++++ action/schema/string_attribute.go | 169 +++ action/schema/string_attribute_test.go | 437 ++++++ action/schema/unlinked_schema.go | 164 +++ action/schema/unlinked_schema_test.go | 1250 +++++++++++++++++ 20 files changed, 5605 insertions(+) create mode 100644 action/schema/attribute.go create mode 100644 action/schema/block.go create mode 100644 action/schema/bool_attribute.go create mode 100644 action/schema/bool_attribute_test.go create mode 100644 action/schema/list_attribute.go create mode 100644 action/schema/list_attribute_test.go create mode 100644 action/schema/nested_attribute.go create mode 100644 action/schema/nested_attribute_object.go create mode 100644 action/schema/nested_attribute_object_test.go create mode 100644 action/schema/nested_block_object.go create mode 100644 action/schema/nested_block_object_test.go create mode 100644 action/schema/schema_type.go create mode 100644 action/schema/single_nested_attribute.go create mode 100644 action/schema/single_nested_attribute_test.go create mode 100644 action/schema/single_nested_block.go create mode 100644 action/schema/single_nested_block_test.go create mode 100644 action/schema/string_attribute.go create mode 100644 action/schema/string_attribute_test.go create mode 100644 action/schema/unlinked_schema.go create mode 100644 action/schema/unlinked_schema_test.go diff --git a/action/schema/attribute.go b/action/schema/attribute.go new file mode 100644 index 000000000..1364b42bc --- /dev/null +++ b/action/schema/attribute.go @@ -0,0 +1,52 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema + +import ( + "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" +) + +// TODO:Actions: Add all of the attribute and nested attribute types listed below +// +// Attribute define a value field inside an action type schema. Implementations in this +// package include: +// - BoolAttribute +// - DynamicAttribute +// - Float32Attribute +// - Float64Attribute +// - Int32Attribute +// - Int64Attribute +// - ListAttribute +// - MapAttribute +// - NumberAttribute +// - ObjectAttribute +// - SetAttribute +// - StringAttribute +// +// Additionally, the NestedAttribute interface extends Attribute with nested +// attributes. Only supported in protocol version 6. Implementations in this +// package include: +// - ListNestedAttribute +// - MapNestedAttribute +// - SetNestedAttribute +// - SingleNestedAttribute +// +// In practitioner configurations, an equals sign (=) is required to set +// the value. [Configuration Reference] +// +// [Configuration Reference]: https://developer.hashicorp.com/terraform/language/syntax/configuration +type Attribute interface { + fwschema.Attribute +} + +// schemaAttributes is an action attribute to fwschema type conversion function. +func schemaAttributes(attributes map[string]Attribute) map[string]fwschema.Attribute { + result := make(map[string]fwschema.Attribute, len(attributes)) + + for name, attribute := range attributes { + result[name] = attribute + } + + return result +} diff --git a/action/schema/block.go b/action/schema/block.go new file mode 100644 index 000000000..ecc291cf0 --- /dev/null +++ b/action/schema/block.go @@ -0,0 +1,43 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema + +import ( + "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" +) + +// TODO:Actions: Add all of the block and nested block types listed below +// +// Block defines a structural field inside an action type schema. Implementations in this +// package include: +// - ListNestedBlock +// - SetNestedBlock +// - SingleNestedBlock +// +// In practitioner configurations, an equals sign (=) cannot be used to set the +// value. Blocks are instead repeated as necessary, or require the use of +// [Dynamic Block Expressions]. +// +// Prefer NestedAttribute over Block. Blocks should typically be used for +// configuration compatibility with previously existing schemas from an older +// Terraform Plugin SDK. Efforts should be made to convert from Block to +// NestedAttribute as a breaking change for practitioners. +// +// [Dynamic Block Expressions]: https://developer.hashicorp.com/terraform/language/expressions/dynamic-blocks +// +// [Configuration Reference]: https://developer.hashicorp.com/terraform/language/syntax/configuration +type Block interface { + fwschema.Block +} + +// schemaBlocks is an action block to fwschema type conversion function. +func schemaBlocks(blocks map[string]Block) map[string]fwschema.Block { + result := make(map[string]fwschema.Block, len(blocks)) + + for name, block := range blocks { + result[name] = block + } + + return result +} diff --git a/action/schema/bool_attribute.go b/action/schema/bool_attribute.go new file mode 100644 index 000000000..5558946e6 --- /dev/null +++ b/action/schema/bool_attribute.go @@ -0,0 +1,170 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema + +import ( + "github.com/hashicorp/terraform-plugin-go/tftypes" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" +) + +// Ensure the implementation satisfies the desired interfaces. +var ( + _ Attribute = BoolAttribute{} +) + +// BoolAttribute represents a schema attribute that is a boolean. When +// retrieving the value for this attribute, use types.Bool as the value type +// unless the CustomType field is set. +// +// Terraform configurations configure this attribute using expressions that +// return a boolean or directly via the true/false keywords. +// +// example_attribute = true +// +// Terraform configurations reference this attribute using the attribute name. +// +// .example_attribute +type BoolAttribute struct { + // CustomType enables the use of a custom attribute type in place of the + // default basetypes.BoolType. When retrieving data, the basetypes.BoolValuable + // associated with this custom type must be used in place of types.Bool. + CustomType basetypes.BoolTypable + + // Required indicates whether the practitioner must enter a value for + // this attribute or not. Required and Optional cannot both be true, + // and Required and Computed cannot both be true. + Required bool + + // Optional indicates whether the practitioner can choose to enter a value + // for this attribute or not. Optional and Required cannot both be true. + Optional bool + + // Description is used in various tooling, like the language server, to + // give practitioners more information about what this attribute is, + // what it's for, and how it should be used. It should be written as + // plain text, with no special formatting. + Description string + + // MarkdownDescription is used in various tooling, like the + // documentation generator, to give practitioners more information + // about what this attribute is, what it's for, and how it should be + // used. It should be formatted using Markdown. + MarkdownDescription string + + // DeprecationMessage defines warning diagnostic details to display when + // practitioner configurations use this Attribute. The warning diagnostic + // summary is automatically set to "Attribute Deprecated" along with + // configuration source file and line information. + // + // Set this field to a practitioner actionable message such as: + // + // - "Configure other_attribute instead. This attribute will be removed + // in the next major version of the provider." + // - "Remove this attribute's configuration as it no longer is used and + // the attribute will be removed in the next major version of the + // provider." + // + // In Terraform 1.2.7 and later, this warning diagnostic is displayed any + // time a practitioner attempts to configure a value for this attribute and + // certain scenarios where this attribute is referenced. + // + // In Terraform 1.2.6 and earlier, this warning diagnostic is only + // displayed when the Attribute is Required or Optional, and if the + // practitioner configuration sets the value to a known or unknown value + // (which may eventually be null). + // + // Across any Terraform version, there are no warnings raised for + // practitioner configuration values set directly to null, as there is no + // way for the framework to differentiate between an unset and null + // configuration due to how Terraform sends configuration information + // across the protocol. + // + // Additional information about deprecation enhancements for read-only + // attributes can be found in: + // + // - https://github.com/hashicorp/terraform/issues/7569 + // + DeprecationMessage string +} + +// ApplyTerraform5AttributePathStep always returns an error as it is not +// possible to step further into a BoolAttribute. +func (a BoolAttribute) ApplyTerraform5AttributePathStep(step tftypes.AttributePathStep) (interface{}, error) { + return a.GetType().ApplyTerraform5AttributePathStep(step) +} + +// Equal returns true if the given Attribute is a BoolAttribute +// and all fields are equal. +func (a BoolAttribute) Equal(o fwschema.Attribute) bool { + if _, ok := o.(BoolAttribute); !ok { + return false + } + + return fwschema.AttributesEqual(a, o) +} + +// GetDeprecationMessage returns the DeprecationMessage field value. +func (a BoolAttribute) GetDeprecationMessage() string { + return a.DeprecationMessage +} + +// GetDescription returns the Description field value. +func (a BoolAttribute) GetDescription() string { + return a.Description +} + +// GetMarkdownDescription returns the MarkdownDescription field value. +func (a BoolAttribute) GetMarkdownDescription() string { + return a.MarkdownDescription +} + +// GetType returns types.StringType or the CustomType field value if defined. +func (a BoolAttribute) GetType() attr.Type { + if a.CustomType != nil { + return a.CustomType + } + + return types.BoolType +} + +// IsComputed always returns false as action schema attributes cannot be Computed. +func (a BoolAttribute) IsComputed() bool { + return false +} + +// IsOptional returns the Optional field value. +func (a BoolAttribute) IsOptional() bool { + return a.Optional +} + +// IsRequired returns the Required field value. +func (a BoolAttribute) IsRequired() bool { + return a.Required +} + +// IsSensitive always returns false as action schema attributes cannot be Sensitive. +func (a BoolAttribute) IsSensitive() bool { + return false +} + +// IsWriteOnly always returns false as action schema attributes cannot be WriteOnly. +func (a BoolAttribute) IsWriteOnly() bool { + return false +} + +// IsRequiredForImport returns false as this behavior is only relevant +// for managed resource identity schema attributes. +func (a BoolAttribute) IsRequiredForImport() bool { + return false +} + +// IsOptionalForImport returns false as this behavior is only relevant +// for managed resource identity schema attributes. +func (a BoolAttribute) IsOptionalForImport() bool { + return false +} diff --git a/action/schema/bool_attribute_test.go b/action/schema/bool_attribute_test.go new file mode 100644 index 000000000..725f14c8b --- /dev/null +++ b/action/schema/bool_attribute_test.go @@ -0,0 +1,437 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema_test + +import ( + "fmt" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-go/tftypes" + + "github.com/hashicorp/terraform-plugin-framework/action/schema" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" + "github.com/hashicorp/terraform-plugin-framework/internal/testing/testschema" + "github.com/hashicorp/terraform-plugin-framework/internal/testing/testtypes" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +func TestBoolAttributeApplyTerraform5AttributePathStep(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute schema.BoolAttribute + step tftypes.AttributePathStep + expected any + expectedError error + }{ + "AttributeName": { + attribute: schema.BoolAttribute{}, + step: tftypes.AttributeName("test"), + expected: nil, + expectedError: fmt.Errorf("cannot apply AttributePathStep tftypes.AttributeName to basetypes.BoolType"), + }, + "ElementKeyInt": { + attribute: schema.BoolAttribute{}, + step: tftypes.ElementKeyInt(1), + expected: nil, + expectedError: fmt.Errorf("cannot apply AttributePathStep tftypes.ElementKeyInt to basetypes.BoolType"), + }, + "ElementKeyString": { + attribute: schema.BoolAttribute{}, + step: tftypes.ElementKeyString("test"), + expected: nil, + expectedError: fmt.Errorf("cannot apply AttributePathStep tftypes.ElementKeyString to basetypes.BoolType"), + }, + "ElementKeyValue": { + attribute: schema.BoolAttribute{}, + step: tftypes.ElementKeyValue(tftypes.NewValue(tftypes.String, "test")), + expected: nil, + expectedError: fmt.Errorf("cannot apply AttributePathStep tftypes.ElementKeyValue to basetypes.BoolType"), + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got, err := testCase.attribute.ApplyTerraform5AttributePathStep(testCase.step) + + if err != nil { + if testCase.expectedError == nil { + t.Fatalf("expected no error, got: %s", err) + } + + if !strings.Contains(err.Error(), testCase.expectedError.Error()) { + t.Fatalf("expected error %q, got: %s", testCase.expectedError, err) + } + } + + if err == nil && testCase.expectedError != nil { + t.Fatalf("got no error, expected: %s", testCase.expectedError) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestBoolAttributeGetDeprecationMessage(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute schema.BoolAttribute + expected string + }{ + "no-deprecation-message": { + attribute: schema.BoolAttribute{}, + expected: "", + }, + "deprecation-message": { + attribute: schema.BoolAttribute{ + DeprecationMessage: "test deprecation message", + }, + expected: "test deprecation message", + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.attribute.GetDeprecationMessage() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestBoolAttributeEqual(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute schema.BoolAttribute + other fwschema.Attribute + expected bool + }{ + "different-type": { + attribute: schema.BoolAttribute{}, + other: testschema.AttributeWithBoolValidators{}, + expected: false, + }, + "equal": { + attribute: schema.BoolAttribute{}, + other: schema.BoolAttribute{}, + expected: true, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.attribute.Equal(testCase.other) + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestBoolAttributeGetDescription(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute schema.BoolAttribute + expected string + }{ + "no-description": { + attribute: schema.BoolAttribute{}, + expected: "", + }, + "description": { + attribute: schema.BoolAttribute{ + Description: "test description", + }, + expected: "test description", + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.attribute.GetDescription() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestBoolAttributeGetMarkdownDescription(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute schema.BoolAttribute + expected string + }{ + "no-markdown-description": { + attribute: schema.BoolAttribute{}, + expected: "", + }, + "markdown-description": { + attribute: schema.BoolAttribute{ + MarkdownDescription: "test description", + }, + expected: "test description", + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.attribute.GetMarkdownDescription() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestBoolAttributeGetType(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute schema.BoolAttribute + expected attr.Type + }{ + "base": { + attribute: schema.BoolAttribute{}, + expected: types.BoolType, + }, + "custom-type": { + attribute: schema.BoolAttribute{ + CustomType: testtypes.BoolType{}, + }, + expected: testtypes.BoolType{}, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.attribute.GetType() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestBoolAttributeIsComputed(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute schema.BoolAttribute + expected bool + }{ + "not-computed": { + attribute: schema.BoolAttribute{}, + expected: false, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.attribute.IsComputed() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestBoolAttributeIsOptional(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute schema.BoolAttribute + expected bool + }{ + "not-optional": { + attribute: schema.BoolAttribute{}, + expected: false, + }, + "optional": { + attribute: schema.BoolAttribute{ + Optional: true, + }, + expected: true, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.attribute.IsOptional() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestBoolAttributeIsRequired(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute schema.BoolAttribute + expected bool + }{ + "not-required": { + attribute: schema.BoolAttribute{}, + expected: false, + }, + "required": { + attribute: schema.BoolAttribute{ + Required: true, + }, + expected: true, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.attribute.IsRequired() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestBoolAttributeIsSensitive(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute schema.BoolAttribute + expected bool + }{ + "not-sensitive": { + attribute: schema.BoolAttribute{}, + expected: false, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.attribute.IsSensitive() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestBoolAttributeIsWriteOnly(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute schema.BoolAttribute + expected bool + }{ + "not-writeOnly": { + attribute: schema.BoolAttribute{}, + expected: false, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.attribute.IsWriteOnly() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestBoolAttributeIsRequiredForImport(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute schema.BoolAttribute + expected bool + }{ + "not-requiredForImport": { + attribute: schema.BoolAttribute{}, + expected: false, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.attribute.IsRequiredForImport() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestBoolAttributeIsOptionalForImport(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute schema.BoolAttribute + expected bool + }{ + "not-optionalForImport": { + attribute: schema.BoolAttribute{}, + expected: false, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.attribute.IsOptionalForImport() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} diff --git a/action/schema/list_attribute.go b/action/schema/list_attribute.go new file mode 100644 index 000000000..180664e9e --- /dev/null +++ b/action/schema/list_attribute.go @@ -0,0 +1,204 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-go/tftypes" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" + "github.com/hashicorp/terraform-plugin-framework/internal/fwtype" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" +) + +// Ensure the implementation satisfies the desired interfaces. +var ( + _ Attribute = ListAttribute{} + _ fwschema.AttributeWithValidateImplementation = ListAttribute{} +) + +// ListAttribute represents a schema attribute that is a list with a single +// element type. When retrieving the value for this attribute, use types.List +// as the value type unless the CustomType field is set. The ElementType field +// must be set. +// +// Use ListNestedAttribute if the underlying elements should be objects and +// require definition beyond type information. +// +// Terraform configurations configure this attribute using expressions that +// return a list or directly via square brace syntax. +// +// # list of strings +// example_attribute = ["first", "second"] +// +// Terraform configurations reference this attribute using expressions that +// accept a list or an element directly via square brace 0-based index syntax: +// +// # first known element +// .example_attribute[0] +type ListAttribute struct { + // ElementType is the type for all elements of the list. This field must be + // set. + // + // Element types that contain a dynamic type (i.e. types.Dynamic) are not supported. + // If underlying dynamic values are required, replace this attribute definition with + // DynamicAttribute instead. + ElementType attr.Type + + // CustomType enables the use of a custom attribute type in place of the + // default basetypes.ListType. When retrieving data, the basetypes.ListValuable + // associated with this custom type must be used in place of types.List. + CustomType basetypes.ListTypable + + // Required indicates whether the practitioner must enter a value for + // this attribute or not. Required and Optional cannot both be true. + Required bool + + // Optional indicates whether the practitioner can choose to enter a value + // for this attribute or not. Optional and Required cannot both be true. + Optional bool + + // Description is used in various tooling, like the language server, to + // give practitioners more information about what this attribute is, + // what it's for, and how it should be used. It should be written as + // plain text, with no special formatting. + Description string + + // MarkdownDescription is used in various tooling, like the + // documentation generator, to give practitioners more information + // about what this attribute is, what it's for, and how it should be + // used. It should be formatted using Markdown. + MarkdownDescription string + + // DeprecationMessage defines warning diagnostic details to display when + // practitioner configurations use this Attribute. The warning diagnostic + // summary is automatically set to "Attribute Deprecated" along with + // configuration source file and line information. + // + // Set this field to a practitioner actionable message such as: + // + // - "Configure other_attribute instead. This attribute will be removed + // in the next major version of the provider." + // - "Remove this attribute's configuration as it no longer is used and + // the attribute will be removed in the next major version of the + // provider." + // + // In Terraform 1.2.7 and later, this warning diagnostic is displayed any + // time a practitioner attempts to configure a value for this attribute and + // certain scenarios where this attribute is referenced. + // + // In Terraform 1.2.6 and earlier, this warning diagnostic is only + // displayed when the Attribute is Required or Optional, and if the + // practitioner configuration sets the value to a known or unknown value + // (which may eventually be null). + // + // Across any Terraform version, there are no warnings raised for + // practitioner configuration values set directly to null, as there is no + // way for the framework to differentiate between an unset and null + // configuration due to how Terraform sends configuration information + // across the protocol. + // + // Additional information about deprecation enhancements for read-only + // attributes can be found in: + // + // - https://github.com/hashicorp/terraform/issues/7569 + // + DeprecationMessage string +} + +// ApplyTerraform5AttributePathStep returns the result of stepping into a list +// index or an error. +func (a ListAttribute) ApplyTerraform5AttributePathStep(step tftypes.AttributePathStep) (interface{}, error) { + return a.GetType().ApplyTerraform5AttributePathStep(step) +} + +// Equal returns true if the given Attribute is a ListAttribute +// and all fields are equal. +func (a ListAttribute) Equal(o fwschema.Attribute) bool { + if _, ok := o.(ListAttribute); !ok { + return false + } + + return fwschema.AttributesEqual(a, o) +} + +// GetDeprecationMessage returns the DeprecationMessage field value. +func (a ListAttribute) GetDeprecationMessage() string { + return a.DeprecationMessage +} + +// GetDescription returns the Description field value. +func (a ListAttribute) GetDescription() string { + return a.Description +} + +// GetMarkdownDescription returns the MarkdownDescription field value. +func (a ListAttribute) GetMarkdownDescription() string { + return a.MarkdownDescription +} + +// GetType returns types.ListType or the CustomType field value if defined. +func (a ListAttribute) GetType() attr.Type { + if a.CustomType != nil { + return a.CustomType + } + + return types.ListType{ + ElemType: a.ElementType, + } +} + +// IsComputed always returns false as action schema attributes cannot be Computed. +func (a ListAttribute) IsComputed() bool { + return false +} + +// IsOptional returns the Optional field value. +func (a ListAttribute) IsOptional() bool { + return a.Optional +} + +// IsRequired returns the Required field value. +func (a ListAttribute) IsRequired() bool { + return a.Required +} + +// IsSensitive always returns false as action schema attributes cannot be Sensitive. +func (a ListAttribute) IsSensitive() bool { + return false +} + +// IsWriteOnly always returns false as action schema attributes cannot be WriteOnly. +func (a ListAttribute) IsWriteOnly() bool { + return false +} + +// IsRequiredForImport returns false as this behavior is only relevant +// for managed resource identity schema attributes. +func (a ListAttribute) IsRequiredForImport() bool { + return false +} + +// IsOptionalForImport returns false as this behavior is only relevant +// for managed resource identity schema attributes. +func (a ListAttribute) IsOptionalForImport() bool { + return false +} + +// ValidateImplementation contains logic for validating the +// provider-defined implementation of the attribute to prevent unexpected +// errors or panics. This logic runs during the GetProviderSchema RPC and +// should never include false positives. +func (a ListAttribute) ValidateImplementation(ctx context.Context, req fwschema.ValidateImplementationRequest, resp *fwschema.ValidateImplementationResponse) { + if a.CustomType == nil && a.ElementType == nil { + resp.Diagnostics.Append(fwschema.AttributeMissingElementTypeDiag(req.Path)) + } + + if a.CustomType == nil && fwtype.ContainsCollectionWithDynamic(a.GetType()) { + resp.Diagnostics.Append(fwtype.AttributeCollectionWithDynamicTypeDiag(req.Path)) + } +} diff --git a/action/schema/list_attribute_test.go b/action/schema/list_attribute_test.go new file mode 100644 index 000000000..52d8ec503 --- /dev/null +++ b/action/schema/list_attribute_test.go @@ -0,0 +1,522 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema_test + +import ( + "context" + "fmt" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-go/tftypes" + + "github.com/hashicorp/terraform-plugin-framework/action/schema" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" + "github.com/hashicorp/terraform-plugin-framework/internal/testing/testschema" + "github.com/hashicorp/terraform-plugin-framework/internal/testing/testtypes" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +func TestListAttributeApplyTerraform5AttributePathStep(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute schema.ListAttribute + step tftypes.AttributePathStep + expected any + expectedError error + }{ + "AttributeName": { + attribute: schema.ListAttribute{ElementType: types.StringType}, + step: tftypes.AttributeName("test"), + expected: nil, + expectedError: fmt.Errorf("cannot apply step tftypes.AttributeName to ListType"), + }, + "ElementKeyInt": { + attribute: schema.ListAttribute{ElementType: types.StringType}, + step: tftypes.ElementKeyInt(1), + expected: types.StringType, + expectedError: nil, + }, + "ElementKeyString": { + attribute: schema.ListAttribute{ElementType: types.StringType}, + step: tftypes.ElementKeyString("test"), + expected: nil, + expectedError: fmt.Errorf("cannot apply step tftypes.ElementKeyString to ListType"), + }, + "ElementKeyValue": { + attribute: schema.ListAttribute{ElementType: types.StringType}, + step: tftypes.ElementKeyValue(tftypes.NewValue(tftypes.String, "test")), + expected: nil, + expectedError: fmt.Errorf("cannot apply step tftypes.ElementKeyValue to ListType"), + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got, err := testCase.attribute.ApplyTerraform5AttributePathStep(testCase.step) + + if err != nil { + if testCase.expectedError == nil { + t.Fatalf("expected no error, got: %s", err) + } + + if !strings.Contains(err.Error(), testCase.expectedError.Error()) { + t.Fatalf("expected error %q, got: %s", testCase.expectedError, err) + } + } + + if err == nil && testCase.expectedError != nil { + t.Fatalf("got no error, expected: %s", testCase.expectedError) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestListAttributeGetDeprecationMessage(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute schema.ListAttribute + expected string + }{ + "no-deprecation-message": { + attribute: schema.ListAttribute{ElementType: types.StringType}, + expected: "", + }, + "deprecation-message": { + attribute: schema.ListAttribute{ + DeprecationMessage: "test deprecation message", + }, + expected: "test deprecation message", + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.attribute.GetDeprecationMessage() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestListAttributeEqual(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute schema.ListAttribute + other fwschema.Attribute + expected bool + }{ + "different-type": { + attribute: schema.ListAttribute{ElementType: types.StringType}, + other: testschema.AttributeWithListValidators{}, + expected: false, + }, + "different-element-type": { + attribute: schema.ListAttribute{ElementType: types.StringType}, + other: schema.ListAttribute{ElementType: types.BoolType}, + expected: false, + }, + "equal": { + attribute: schema.ListAttribute{ElementType: types.StringType}, + other: schema.ListAttribute{ElementType: types.StringType}, + expected: true, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.attribute.Equal(testCase.other) + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestListAttributeGetDescription(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute schema.ListAttribute + expected string + }{ + "no-description": { + attribute: schema.ListAttribute{ElementType: types.StringType}, + expected: "", + }, + "description": { + attribute: schema.ListAttribute{ + Description: "test description", + }, + expected: "test description", + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.attribute.GetDescription() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestListAttributeGetMarkdownDescription(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute schema.ListAttribute + expected string + }{ + "no-markdown-description": { + attribute: schema.ListAttribute{ElementType: types.StringType}, + expected: "", + }, + "markdown-description": { + attribute: schema.ListAttribute{ + MarkdownDescription: "test description", + }, + expected: "test description", + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.attribute.GetMarkdownDescription() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestListAttributeGetType(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute schema.ListAttribute + expected attr.Type + }{ + "base": { + attribute: schema.ListAttribute{ElementType: types.StringType}, + expected: types.ListType{ElemType: types.StringType}, + }, + "custom-type": { + attribute: schema.ListAttribute{ + CustomType: testtypes.ListType{ListType: types.ListType{ElemType: types.StringType}}, + }, + expected: testtypes.ListType{ListType: types.ListType{ElemType: types.StringType}}, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.attribute.GetType() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestListAttributeIsComputed(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute schema.ListAttribute + expected bool + }{ + "not-computed": { + attribute: schema.ListAttribute{ElementType: types.StringType}, + expected: false, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.attribute.IsComputed() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestListAttributeIsOptional(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute schema.ListAttribute + expected bool + }{ + "not-optional": { + attribute: schema.ListAttribute{ElementType: types.StringType}, + expected: false, + }, + "optional": { + attribute: schema.ListAttribute{ + Optional: true, + }, + expected: true, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.attribute.IsOptional() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestListAttributeIsRequired(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute schema.ListAttribute + expected bool + }{ + "not-required": { + attribute: schema.ListAttribute{ElementType: types.StringType}, + expected: false, + }, + "required": { + attribute: schema.ListAttribute{ + Required: true, + }, + expected: true, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.attribute.IsRequired() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestListAttributeIsSensitive(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute schema.ListAttribute + expected bool + }{ + "not-sensitive": { + attribute: schema.ListAttribute{ElementType: types.StringType}, + expected: false, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.attribute.IsSensitive() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestListAttributeIsWriteOnly(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute schema.ListAttribute + expected bool + }{ + "not-writeOnly": { + attribute: schema.ListAttribute{}, + expected: false, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.attribute.IsWriteOnly() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestListAttributeValidateImplementation(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute schema.ListAttribute + request fwschema.ValidateImplementationRequest + expected *fwschema.ValidateImplementationResponse + }{ + "elementtype": { + attribute: schema.ListAttribute{ + Required: true, + ElementType: types.StringType, + }, + request: fwschema.ValidateImplementationRequest{ + Name: "test", + Path: path.Root("test"), + }, + expected: &fwschema.ValidateImplementationResponse{}, + }, + "elementtype-dynamic": { + attribute: schema.ListAttribute{ + Required: true, + ElementType: types.DynamicType, + }, + request: fwschema.ValidateImplementationRequest{ + Name: "test", + Path: path.Root("test"), + }, + expected: &fwschema.ValidateImplementationResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Invalid Schema Implementation", + "When validating the schema, an implementation issue was found. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + "\"test\" is an attribute that contains a collection type with a nested dynamic type.\n\n"+ + "Dynamic types inside of collections are not currently supported in terraform-plugin-framework. "+ + "If underlying dynamic values are required, replace the \"test\" attribute definition with DynamicAttribute instead.", + ), + }, + }, + }, + "elementtype-missing": { + attribute: schema.ListAttribute{ + Required: true, + }, + request: fwschema.ValidateImplementationRequest{ + Name: "test", + Path: path.Root("test"), + }, + expected: &fwschema.ValidateImplementationResponse{ + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Invalid Attribute Implementation", + "When validating the schema, an implementation issue was found. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + "\"test\" is missing the CustomType or ElementType field on a collection Attribute. "+ + "One of these fields is required to prevent other unexpected errors or panics.", + ), + }, + }, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := &fwschema.ValidateImplementationResponse{} + testCase.attribute.ValidateImplementation(context.Background(), testCase.request, got) + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestListAttributeIsRequiredForImport(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute schema.ListAttribute + expected bool + }{ + "not-requiredForImport": { + attribute: schema.ListAttribute{}, + expected: false, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.attribute.IsRequiredForImport() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestListAttributeIsOptionalForImport(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute schema.ListAttribute + expected bool + }{ + "not-optionalForImport": { + attribute: schema.ListAttribute{}, + expected: false, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.attribute.IsOptionalForImport() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} diff --git a/action/schema/nested_attribute.go b/action/schema/nested_attribute.go new file mode 100644 index 000000000..31d2ee158 --- /dev/null +++ b/action/schema/nested_attribute.go @@ -0,0 +1,14 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema + +import ( + "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" +) + +// Nested attributes are only compatible with protocol version 6. +type NestedAttribute interface { + Attribute + fwschema.NestedAttribute +} diff --git a/action/schema/nested_attribute_object.go b/action/schema/nested_attribute_object.go new file mode 100644 index 000000000..b082a154f --- /dev/null +++ b/action/schema/nested_attribute_object.go @@ -0,0 +1,65 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema + +import ( + "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +// Ensure the implementation satisifies the desired interfaces. +var ( + _ fwschema.NestedAttributeObject = NestedAttributeObject{} +) + +// NestedAttributeObject is the object containing the underlying attributes +// for a ListNestedAttribute, MapNestedAttribute, SetNestedAttribute, or +// SingleNestedAttribute (automatically generated). When retrieving the value +// for this attribute, use types.Object as the value type unless the CustomType +// field is set. The Attributes field must be set. Nested attributes are only +// compatible with protocol version 6. +// +// This object enables customizing and simplifying details within its parent +// NestedAttribute, therefore it cannot have Terraform schema fields such as +// Required, Description, etc. +type NestedAttributeObject struct { + // Attributes is the mapping of underlying attribute names to attribute + // definitions. This field must be set. + Attributes map[string]Attribute + + // CustomType enables the use of a custom attribute type in place of the + // default basetypes.ObjectType. When retrieving data, the basetypes.ObjectValuable + // associated with this custom type must be used in place of types.Object. + CustomType basetypes.ObjectTypable +} + +// ApplyTerraform5AttributePathStep performs an AttributeName step on the +// underlying attributes or returns an error. +func (o NestedAttributeObject) ApplyTerraform5AttributePathStep(step tftypes.AttributePathStep) (any, error) { + return fwschema.NestedAttributeObjectApplyTerraform5AttributePathStep(o, step) +} + +// Equal returns true if the given NestedAttributeObject is equivalent. +func (o NestedAttributeObject) Equal(other fwschema.NestedAttributeObject) bool { + if _, ok := other.(NestedAttributeObject); !ok { + return false + } + + return fwschema.NestedAttributeObjectEqual(o, other) +} + +// GetAttributes returns the Attributes field value. +func (o NestedAttributeObject) GetAttributes() fwschema.UnderlyingAttributes { + return schemaAttributes(o.Attributes) +} + +// Type returns the framework type of the NestedAttributeObject. +func (o NestedAttributeObject) Type() basetypes.ObjectTypable { + if o.CustomType != nil { + return o.CustomType + } + + return fwschema.NestedAttributeObjectType(o) +} diff --git a/action/schema/nested_attribute_object_test.go b/action/schema/nested_attribute_object_test.go new file mode 100644 index 000000000..8c39be9e5 --- /dev/null +++ b/action/schema/nested_attribute_object_test.go @@ -0,0 +1,233 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema_test + +import ( + "fmt" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/action/schema" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" + "github.com/hashicorp/terraform-plugin-framework/internal/testing/testtypes" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +func TestNestedAttributeObjectApplyTerraform5AttributePathStep(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + object schema.NestedAttributeObject + step tftypes.AttributePathStep + expected any + expectedError error + }{ + "AttributeName": { + object: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + step: tftypes.AttributeName("testattr"), + expected: schema.StringAttribute{}, + expectedError: nil, + }, + "AttributeName-missing": { + object: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + step: tftypes.AttributeName("other"), + expected: nil, + expectedError: fmt.Errorf("no attribute \"other\" on NestedAttributeObject"), + }, + "ElementKeyInt": { + object: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + step: tftypes.ElementKeyInt(1), + expected: nil, + expectedError: fmt.Errorf("cannot apply AttributePathStep tftypes.ElementKeyInt to NestedAttributeObject"), + }, + "ElementKeyString": { + object: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + step: tftypes.ElementKeyString("test"), + expected: nil, + expectedError: fmt.Errorf("cannot apply AttributePathStep tftypes.ElementKeyString to NestedAttributeObject"), + }, + "ElementKeyValue": { + object: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + step: tftypes.ElementKeyValue(tftypes.NewValue(tftypes.String, "test")), + expected: nil, + expectedError: fmt.Errorf("cannot apply AttributePathStep tftypes.ElementKeyValue to NestedAttributeObject"), + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got, err := testCase.object.ApplyTerraform5AttributePathStep(testCase.step) + + if err != nil { + if testCase.expectedError == nil { + t.Fatalf("expected no error, got: %s", err) + } + + if !strings.Contains(err.Error(), testCase.expectedError.Error()) { + t.Fatalf("expected error %q, got: %s", testCase.expectedError, err) + } + } + + if err == nil && testCase.expectedError != nil { + t.Fatalf("got no error, expected: %s", testCase.expectedError) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestNestedAttributeObjectEqual(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + object schema.NestedAttributeObject + other fwschema.NestedAttributeObject + expected bool + }{ + "different-attributes": { + object: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + other: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.BoolAttribute{}, + }, + }, + expected: false, + }, + "equal": { + object: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + other: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + expected: true, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.object.Equal(testCase.other) + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestNestedAttributeObjectGetAttributes(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + object schema.NestedAttributeObject + expected fwschema.UnderlyingAttributes + }{ + "no-attributes": { + object: schema.NestedAttributeObject{}, + expected: fwschema.UnderlyingAttributes{}, + }, + "attributes": { + object: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "testattr1": schema.StringAttribute{}, + "testattr2": schema.StringAttribute{}, + }, + }, + expected: fwschema.UnderlyingAttributes{ + "testattr1": schema.StringAttribute{}, + "testattr2": schema.StringAttribute{}, + }, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.object.GetAttributes() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestNestedAttributeObjectType(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + object schema.NestedAttributeObject + expected attr.Type + }{ + "base": { + object: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + expected: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "testattr": types.StringType, + }, + }, + }, + "custom-type": { + object: schema.NestedAttributeObject{ + CustomType: testtypes.ObjectType{}, + }, + expected: testtypes.ObjectType{}, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.object.Type() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} diff --git a/action/schema/nested_block_object.go b/action/schema/nested_block_object.go new file mode 100644 index 000000000..8193b6891 --- /dev/null +++ b/action/schema/nested_block_object.go @@ -0,0 +1,77 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema + +import ( + "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +// Ensure the implementation satisifies the desired interfaces. +var ( + _ fwschema.NestedBlockObject = NestedBlockObject{} +) + +// NestedBlockObject is the object containing the underlying attributes and +// blocks for a ListNestedBlock or SetNestedBlock. When retrieving the value +// for this attribute, use types.Object as the value type unless the CustomType +// field is set. +// +// This object enables customizing and simplifying details within its parent +// Block, therefore it cannot have Terraform schema fields such as Description, +// etc. +type NestedBlockObject struct { + // Attributes is the mapping of underlying attribute names to attribute + // definitions. + // + // Names must only contain lowercase letters, numbers, and underscores. + // Names must not collide with any Blocks names. + Attributes map[string]Attribute + + // Blocks is the mapping of underlying block names to block definitions. + // + // Names must only contain lowercase letters, numbers, and underscores. + // Names must not collide with any Attributes names. + Blocks map[string]Block + + // CustomType enables the use of a custom attribute type in place of the + // default basetypes.ObjectType. When retrieving data, the basetypes.ObjectValuable + // associated with this custom type must be used in place of types.Object. + CustomType basetypes.ObjectTypable +} + +// ApplyTerraform5AttributePathStep performs an AttributeName step on the +// underlying attributes or returns an error. +func (o NestedBlockObject) ApplyTerraform5AttributePathStep(step tftypes.AttributePathStep) (any, error) { + return fwschema.NestedBlockObjectApplyTerraform5AttributePathStep(o, step) +} + +// Equal returns true if the given NestedBlockObject is equivalent. +func (o NestedBlockObject) Equal(other fwschema.NestedBlockObject) bool { + if _, ok := other.(NestedBlockObject); !ok { + return false + } + + return fwschema.NestedBlockObjectEqual(o, other) +} + +// GetAttributes returns the Attributes field value. +func (o NestedBlockObject) GetAttributes() fwschema.UnderlyingAttributes { + return schemaAttributes(o.Attributes) +} + +// GetAttributes returns the Blocks field value. +func (o NestedBlockObject) GetBlocks() map[string]fwschema.Block { + return schemaBlocks(o.Blocks) +} + +// Type returns the framework type of the NestedBlockObject. +func (o NestedBlockObject) Type() basetypes.ObjectTypable { + if o.CustomType != nil { + return o.CustomType + } + + return fwschema.NestedBlockObjectType(o) +} diff --git a/action/schema/nested_block_object_test.go b/action/schema/nested_block_object_test.go new file mode 100644 index 000000000..2139384e1 --- /dev/null +++ b/action/schema/nested_block_object_test.go @@ -0,0 +1,317 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema_test + +import ( + "fmt" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/action/schema" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" + "github.com/hashicorp/terraform-plugin-framework/internal/testing/testtypes" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +func TestNestedBlockObjectApplyTerraform5AttributePathStep(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + object schema.NestedBlockObject + step tftypes.AttributePathStep + expected any + expectedError error + }{ + "AttributeName-attribute": { + object: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + step: tftypes.AttributeName("testattr"), + expected: schema.StringAttribute{}, + expectedError: nil, + }, + "AttributeName-block": { + object: schema.NestedBlockObject{ + Blocks: map[string]schema.Block{ + "testblock": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + }, + }, + step: tftypes.AttributeName("testblock"), + expected: schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + expectedError: nil, + }, + "AttributeName-missing": { + object: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + step: tftypes.AttributeName("other"), + expected: nil, + expectedError: fmt.Errorf("no attribute or block \"other\" on NestedBlockObject"), + }, + "ElementKeyInt": { + object: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + step: tftypes.ElementKeyInt(1), + expected: nil, + expectedError: fmt.Errorf("cannot apply AttributePathStep tftypes.ElementKeyInt to NestedBlockObject"), + }, + "ElementKeyString": { + object: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + step: tftypes.ElementKeyString("test"), + expected: nil, + expectedError: fmt.Errorf("cannot apply AttributePathStep tftypes.ElementKeyString to NestedBlockObject"), + }, + "ElementKeyValue": { + object: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + step: tftypes.ElementKeyValue(tftypes.NewValue(tftypes.String, "test")), + expected: nil, + expectedError: fmt.Errorf("cannot apply AttributePathStep tftypes.ElementKeyValue to NestedBlockObject"), + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got, err := testCase.object.ApplyTerraform5AttributePathStep(testCase.step) + + if err != nil { + if testCase.expectedError == nil { + t.Fatalf("expected no error, got: %s", err) + } + + if !strings.Contains(err.Error(), testCase.expectedError.Error()) { + t.Fatalf("expected error %q, got: %s", testCase.expectedError, err) + } + } + + if err == nil && testCase.expectedError != nil { + t.Fatalf("got no error, expected: %s", testCase.expectedError) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestNestedBlockObjectEqual(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + object schema.NestedBlockObject + other fwschema.NestedBlockObject + expected bool + }{ + "different-attributes": { + object: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + other: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.BoolAttribute{}, + }, + }, + expected: false, + }, + "equal": { + object: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + other: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + expected: true, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.object.Equal(testCase.other) + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestNestedBlockObjectGetAttributes(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + object schema.NestedBlockObject + expected fwschema.UnderlyingAttributes + }{ + "no-attributes": { + object: schema.NestedBlockObject{}, + expected: fwschema.UnderlyingAttributes{}, + }, + "attributes": { + object: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "testattr1": schema.StringAttribute{}, + "testattr2": schema.StringAttribute{}, + }, + }, + expected: fwschema.UnderlyingAttributes{ + "testattr1": schema.StringAttribute{}, + "testattr2": schema.StringAttribute{}, + }, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.object.GetAttributes() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestNestedBlockObjectGetBlocks(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + object schema.NestedBlockObject + expected map[string]fwschema.Block + }{ + "no-blocks": { + object: schema.NestedBlockObject{}, + expected: map[string]fwschema.Block{}, + }, + "blocks": { + object: schema.NestedBlockObject{ + Blocks: map[string]schema.Block{ + "testblock1": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + "testblock2": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + }, + }, + expected: map[string]fwschema.Block{ + "testblock1": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + "testblock2": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + }, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.object.GetBlocks() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestNestedBlockObjectType(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + object schema.NestedBlockObject + expected attr.Type + }{ + "base": { + object: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + Blocks: map[string]schema.Block{ + "testblock": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + }, + }, + expected: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "testattr": types.StringType, + "testblock": types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "testattr": types.StringType, + }, + }, + }, + }, + }, + "custom-type": { + object: schema.NestedBlockObject{ + CustomType: testtypes.ObjectType{}, + }, + expected: testtypes.ObjectType{}, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.object.Type() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} diff --git a/action/schema/schema_type.go b/action/schema/schema_type.go new file mode 100644 index 000000000..04122adf9 --- /dev/null +++ b/action/schema/schema_type.go @@ -0,0 +1,26 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema + +import "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" + +// TODO:Actions: Implement lifecycle and linked schemas +// +// SchemaType is the interface that an action schema type must implement. Action +// schema types are statically definined in the protocol, so all implementations +// are defined in this package. +// +// SchemaType implementations define how a practitioner can trigger an action, as well +// as what effect the action can have on the state. There are currently three different +// types of actions: +// - [UnlinkedSchema] actions are actions that cannot cause changes to resource states. +// - [LifecycleSchema] actions are actions that can cause changes to exactly one resource state. +// - [LinkedSchema] actions are actions that can cause changes to one or more resource states. +type SchemaType interface { + fwschema.Schema + + // Action schema types are statically defined in the protocol, so this + // interface is not meant to be implemented outside of this package + isActionSchemaType() +} diff --git a/action/schema/single_nested_attribute.go b/action/schema/single_nested_attribute.go new file mode 100644 index 000000000..48903ab02 --- /dev/null +++ b/action/schema/single_nested_attribute.go @@ -0,0 +1,226 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema + +import ( + "fmt" + + "github.com/hashicorp/terraform-plugin-go/tftypes" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" +) + +// Ensure the implementation satisifies the desired interfaces. +var ( + _ NestedAttribute = SingleNestedAttribute{} +) + +// SingleNestedAttribute represents an attribute that is a single object where +// the object attributes can be fully defined, including further nested +// attributes. When retrieving the value for this attribute, use types.Object +// as the value type unless the CustomType field is set. The Attributes field +// must be set. Nested attributes are only compatible with protocol version 6. +// +// Use ObjectAttribute if the underlying attributes do not require definition +// beyond type information. +// +// Terraform configurations configure this attribute using expressions that +// return an object or directly via curly brace syntax. +// +// # single object +// example_attribute = { +// nested_attribute = #... +// } +// +// Terraform configurations reference this attribute using expressions that +// accept an object or an attribute name directly via period syntax: +// +// # object nested_attribute value +// .example_attribute.nested_attribute +type SingleNestedAttribute struct { + // Attributes is the mapping of underlying attribute names to attribute + // definitions. This field must be set. + Attributes map[string]Attribute + + // CustomType enables the use of a custom attribute type in place of the + // default basetypes.ObjectType. When retrieving data, the basetypes.ObjectValuable + // associated with this custom type must be used in place of types.Object. + CustomType basetypes.ObjectTypable + + // Required indicates whether the practitioner must enter a value for + // this attribute or not. Required and Optional cannot both be true. + Required bool + + // Optional indicates whether the practitioner can choose to enter a value + // for this attribute or not. Optional and Required cannot both be true. + Optional bool + + // Description is used in various tooling, like the language server, to + // give practitioners more information about what this attribute is, + // what it's for, and how it should be used. It should be written as + // plain text, with no special formatting. + Description string + + // MarkdownDescription is used in various tooling, like the + // documentation generator, to give practitioners more information + // about what this attribute is, what it's for, and how it should be + // used. It should be formatted using Markdown. + MarkdownDescription string + + // DeprecationMessage defines warning diagnostic details to display when + // practitioner configurations use this Attribute. The warning diagnostic + // summary is automatically set to "Attribute Deprecated" along with + // configuration source file and line information. + // + // Set this field to a practitioner actionable message such as: + // + // - "Configure other_attribute instead. This attribute will be removed + // in the next major version of the provider." + // - "Remove this attribute's configuration as it no longer is used and + // the attribute will be removed in the next major version of the + // provider." + // + // In Terraform 1.2.7 and later, this warning diagnostic is displayed any + // time a practitioner attempts to configure a value for this attribute and + // certain scenarios where this attribute is referenced. + // + // In Terraform 1.2.6 and earlier, this warning diagnostic is only + // displayed when the Attribute is Required or Optional, and if the + // practitioner configuration sets the value to a known or unknown value + // (which may eventually be null). + // + // Across any Terraform version, there are no warnings raised for + // practitioner configuration values set directly to null, as there is no + // way for the framework to differentiate between an unset and null + // configuration due to how Terraform sends configuration information + // across the protocol. + // + // Additional information about deprecation enhancements for read-only + // attributes can be found in: + // + // - https://github.com/hashicorp/terraform/issues/7569 + // + DeprecationMessage string +} + +// ApplyTerraform5AttributePathStep returns the Attributes field value if step +// is AttributeName, otherwise returns an error. +func (a SingleNestedAttribute) ApplyTerraform5AttributePathStep(step tftypes.AttributePathStep) (interface{}, error) { + name, ok := step.(tftypes.AttributeName) + + if !ok { + return nil, fmt.Errorf("cannot apply step %T to SingleNestedAttribute", step) + } + + attribute, ok := a.Attributes[string(name)] + + if !ok { + return nil, fmt.Errorf("no attribute %q on SingleNestedAttribute", name) + } + + return attribute, nil +} + +// Equal returns true if the given Attribute is a SingleNestedAttribute +// and all fields are equal. +func (a SingleNestedAttribute) Equal(o fwschema.Attribute) bool { + other, ok := o.(SingleNestedAttribute) + + if !ok { + return false + } + + return fwschema.NestedAttributesEqual(a, other) +} + +// GetAttributes returns the Attributes field value. +func (a SingleNestedAttribute) GetAttributes() fwschema.UnderlyingAttributes { + return schemaAttributes(a.Attributes) +} + +// GetDeprecationMessage returns the DeprecationMessage field value. +func (a SingleNestedAttribute) GetDeprecationMessage() string { + return a.DeprecationMessage +} + +// GetDescription returns the Description field value. +func (a SingleNestedAttribute) GetDescription() string { + return a.Description +} + +// GetMarkdownDescription returns the MarkdownDescription field value. +func (a SingleNestedAttribute) GetMarkdownDescription() string { + return a.MarkdownDescription +} + +// GetNestedObject returns a generated NestedAttributeObject from the +// Attributes, CustomType, and Validators field values. +func (a SingleNestedAttribute) GetNestedObject() fwschema.NestedAttributeObject { + return NestedAttributeObject{ + Attributes: a.Attributes, + CustomType: a.CustomType, + } +} + +// GetNestingMode always returns NestingModeSingle. +func (a SingleNestedAttribute) GetNestingMode() fwschema.NestingMode { + return fwschema.NestingModeSingle +} + +// GetType returns ListType of ObjectType or CustomType. +func (a SingleNestedAttribute) GetType() attr.Type { + if a.CustomType != nil { + return a.CustomType + } + + attrTypes := make(map[string]attr.Type, len(a.Attributes)) + + for name, attribute := range a.Attributes { + attrTypes[name] = attribute.GetType() + } + + return types.ObjectType{ + AttrTypes: attrTypes, + } +} + +// IsComputed always returns false as action schema attributes cannot be Computed. +func (a SingleNestedAttribute) IsComputed() bool { + return false +} + +// IsOptional returns the Optional field value. +func (a SingleNestedAttribute) IsOptional() bool { + return a.Optional +} + +// IsRequired returns the Required field value. +func (a SingleNestedAttribute) IsRequired() bool { + return a.Required +} + +// IsSensitive always returns false as action schema attributes cannot be Sensitive. +func (a SingleNestedAttribute) IsSensitive() bool { + return false +} + +// IsWriteOnly always returns false as action schema attributes cannot be WriteOnly. +func (a SingleNestedAttribute) IsWriteOnly() bool { + return false +} + +// IsRequiredForImport returns false as this behavior is only relevant +// for managed resource identity schema attributes. +func (a SingleNestedAttribute) IsRequiredForImport() bool { + return false +} + +// IsOptionalForImport returns false as this behavior is only relevant +// for managed resource identity schema attributes. +func (a SingleNestedAttribute) IsOptionalForImport() bool { + return false +} diff --git a/action/schema/single_nested_attribute_test.go b/action/schema/single_nested_attribute_test.go new file mode 100644 index 000000000..a2522611d --- /dev/null +++ b/action/schema/single_nested_attribute_test.go @@ -0,0 +1,575 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema_test + +import ( + "fmt" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-go/tftypes" + + "github.com/hashicorp/terraform-plugin-framework/action/schema" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" + "github.com/hashicorp/terraform-plugin-framework/internal/testing/testschema" + "github.com/hashicorp/terraform-plugin-framework/internal/testing/testtypes" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +func TestSingleNestedAttributeApplyTerraform5AttributePathStep(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute schema.SingleNestedAttribute + step tftypes.AttributePathStep + expected any + expectedError error + }{ + "AttributeName": { + attribute: schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + step: tftypes.AttributeName("testattr"), + expected: schema.StringAttribute{}, + expectedError: nil, + }, + "AttributeName-missing": { + attribute: schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + step: tftypes.AttributeName("other"), + expected: nil, + expectedError: fmt.Errorf("no attribute \"other\" on SingleNestedAttribute"), + }, + "ElementKeyInt": { + attribute: schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + step: tftypes.ElementKeyInt(1), + expected: nil, + expectedError: fmt.Errorf("cannot apply step tftypes.ElementKeyInt to SingleNestedAttribute"), + }, + "ElementKeyString": { + attribute: schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + step: tftypes.ElementKeyString("test"), + expected: nil, + expectedError: fmt.Errorf("cannot apply step tftypes.ElementKeyString to SingleNestedAttribute"), + }, + "ElementKeyValue": { + attribute: schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + step: tftypes.ElementKeyValue(tftypes.NewValue(tftypes.String, "test")), + expected: nil, + expectedError: fmt.Errorf("cannot apply step tftypes.ElementKeyValue to SingleNestedAttribute"), + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got, err := testCase.attribute.ApplyTerraform5AttributePathStep(testCase.step) + + if err != nil { + if testCase.expectedError == nil { + t.Fatalf("expected no error, got: %s", err) + } + + if !strings.Contains(err.Error(), testCase.expectedError.Error()) { + t.Fatalf("expected error %q, got: %s", testCase.expectedError, err) + } + } + + if err == nil && testCase.expectedError != nil { + t.Fatalf("got no error, expected: %s", testCase.expectedError) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestSingleNestedAttributeEqual(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute schema.SingleNestedAttribute + other fwschema.Attribute + expected bool + }{ + "different-type": { + attribute: schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + other: testschema.AttributeWithObjectValidators{}, + expected: false, + }, + "different-attributes-definitions": { + attribute: schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{ + Optional: true, + }, + }, + }, + other: schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{ + Required: true, + }, + }, + }, + expected: false, + }, + "different-attributes-types": { + attribute: schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + other: schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.BoolAttribute{}, + }, + }, + expected: false, + }, + "equal": { + attribute: schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + other: schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + expected: true, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.attribute.Equal(testCase.other) + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestSingleNestedAttributeGetDeprecationMessage(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute schema.SingleNestedAttribute + expected string + }{ + "no-deprecation-message": { + attribute: schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + expected: "", + }, + "deprecation-message": { + attribute: schema.SingleNestedAttribute{ + DeprecationMessage: "test deprecation message", + }, + expected: "test deprecation message", + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.attribute.GetDeprecationMessage() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestSingleNestedAttributeGetDescription(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute schema.SingleNestedAttribute + expected string + }{ + "no-description": { + attribute: schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + expected: "", + }, + "description": { + attribute: schema.SingleNestedAttribute{ + Description: "test description", + }, + expected: "test description", + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.attribute.GetDescription() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestSingleNestedAttributeGetMarkdownDescription(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute schema.SingleNestedAttribute + expected string + }{ + "no-markdown-description": { + attribute: schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + expected: "", + }, + "markdown-description": { + attribute: schema.SingleNestedAttribute{ + MarkdownDescription: "test description", + }, + expected: "test description", + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.attribute.GetMarkdownDescription() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestSingleNestedAttributeGetNestedObject(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute schema.SingleNestedAttribute + expected schema.NestedAttributeObject + }{ + "nested-object": { + attribute: schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + expected: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.attribute.GetNestedObject() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestSingleNestedAttributeGetType(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute schema.SingleNestedAttribute + expected attr.Type + }{ + "base": { + attribute: schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + expected: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "testattr": types.StringType, + }, + }, + }, + "custom-type": { + attribute: schema.SingleNestedAttribute{ + CustomType: testtypes.ObjectType{}, + }, + expected: testtypes.ObjectType{}, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.attribute.GetType() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestSingleNestedAttributeIsComputed(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute schema.SingleNestedAttribute + expected bool + }{ + "not-computed": { + attribute: schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + expected: false, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.attribute.IsComputed() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestSingleNestedAttributeIsOptional(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute schema.SingleNestedAttribute + expected bool + }{ + "not-optional": { + attribute: schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + expected: false, + }, + "optional": { + attribute: schema.SingleNestedAttribute{ + Optional: true, + }, + expected: true, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.attribute.IsOptional() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestSingleNestedAttributeIsRequired(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute schema.SingleNestedAttribute + expected bool + }{ + "not-required": { + attribute: schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + expected: false, + }, + "required": { + attribute: schema.SingleNestedAttribute{ + Required: true, + }, + expected: true, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.attribute.IsRequired() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestSingleNestedAttributeIsSensitive(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute schema.SingleNestedAttribute + expected bool + }{ + "not-sensitive": { + attribute: schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + expected: false, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.attribute.IsSensitive() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestSingleNestedAttributeIsWriteOnly(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute schema.SingleNestedAttribute + expected bool + }{ + "not-writeOnly": { + attribute: schema.SingleNestedAttribute{}, + expected: false, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.attribute.IsWriteOnly() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestSingleNestedAttributeIsRequiredForImport(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute schema.SingleNestedAttribute + expected bool + }{ + "not-requiredForImport": { + attribute: schema.SingleNestedAttribute{}, + expected: false, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.attribute.IsRequiredForImport() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestSingleNestedAttributeIsOptionalForImport(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute schema.SingleNestedAttribute + expected bool + }{ + "not-optionalForImport": { + attribute: schema.SingleNestedAttribute{}, + expected: false, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.attribute.IsOptionalForImport() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} diff --git a/action/schema/single_nested_block.go b/action/schema/single_nested_block.go new file mode 100644 index 000000000..feb6f63d8 --- /dev/null +++ b/action/schema/single_nested_block.go @@ -0,0 +1,191 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema + +import ( + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +// Ensure the implementation satisifies the desired interfaces. +var ( + _ Block = SingleNestedBlock{} +) + +// SingleNestedBlock represents a block that is a single object where +// the object attributes can be fully defined, including further attributes +// or blocks. When retrieving the value for this block, use types.Object +// as the value type unless the CustomType field is set. +// +// Prefer SingleNestedAttribute over SingleNestedBlock if the provider is +// using protocol version 6. Nested attributes allow practitioners to configure +// values directly with expressions. +// +// Terraform configurations configure this block only once using curly brace +// syntax without an equals (=) sign or [Dynamic Block Expressions]. +// +// # single block +// example_block { +// nested_attribute = #... +// } +// +// Terraform configurations reference this block using expressions that +// accept an object or an attribute name directly via period syntax: +// +// # object nested_attribute value +// .example_block.nested_attribute +// +// [Dynamic Block Expressions]: https://developer.hashicorp.com/terraform/language/expressions/dynamic-blocks +type SingleNestedBlock struct { + // Attributes is the mapping of underlying attribute names to attribute + // definitions. + // + // Names must only contain lowercase letters, numbers, and underscores. + // Names must not collide with any Blocks names. + Attributes map[string]Attribute + + // Blocks is the mapping of underlying block names to block definitions. + // + // Names must only contain lowercase letters, numbers, and underscores. + // Names must not collide with any Attributes names. + Blocks map[string]Block + + // CustomType enables the use of a custom attribute type in place of the + // default basetypes.ObjectType. When retrieving data, the basetypes.ObjectValuable + // associated with this custom type must be used in place of types.Object. + CustomType basetypes.ObjectTypable + + // Description is used in various tooling, like the language server, to + // give practitioners more information about what this attribute is, + // what it's for, and how it should be used. It should be written as + // plain text, with no special formatting. + Description string + + // MarkdownDescription is used in various tooling, like the + // documentation generator, to give practitioners more information + // about what this attribute is, what it's for, and how it should be + // used. It should be formatted using Markdown. + MarkdownDescription string + + // DeprecationMessage defines warning diagnostic details to display when + // practitioner configurations use this Attribute. The warning diagnostic + // summary is automatically set to "Attribute Deprecated" along with + // configuration source file and line information. + // + // Set this field to a practitioner actionable message such as: + // + // - "Configure other_attribute instead. This attribute will be removed + // in the next major version of the provider." + // - "Remove this attribute's configuration as it no longer is used and + // the attribute will be removed in the next major version of the + // provider." + // + // In Terraform 1.2.7 and later, this warning diagnostic is displayed any + // time a practitioner attempts to configure a value for this attribute and + // certain scenarios where this attribute is referenced. + // + // In Terraform 1.2.6 and earlier, this warning diagnostic is only + // displayed when the Attribute is Required or Optional, and if the + // practitioner configuration sets the value to a known or unknown value + // (which may eventually be null). + // + // Across any Terraform version, there are no warnings raised for + // practitioner configuration values set directly to null, as there is no + // way for the framework to differentiate between an unset and null + // configuration due to how Terraform sends configuration information + // across the protocol. + // + // Additional information about deprecation enhancements for read-only + // attributes can be found in: + // + // - https://github.com/hashicorp/terraform/issues/7569 + // + DeprecationMessage string +} + +// ApplyTerraform5AttributePathStep returns the Attributes field value if step +// is AttributeName, otherwise returns an error. +func (b SingleNestedBlock) ApplyTerraform5AttributePathStep(step tftypes.AttributePathStep) (interface{}, error) { + name, ok := step.(tftypes.AttributeName) + + if !ok { + return nil, fmt.Errorf("cannot apply step %T to SingleNestedBlock", step) + } + + if attribute, ok := b.Attributes[string(name)]; ok { + return attribute, nil + } + + if block, ok := b.Blocks[string(name)]; ok { + return block, nil + } + + return nil, fmt.Errorf("no attribute or block %q on SingleNestedBlock", name) +} + +// Equal returns true if the given Attribute is b SingleNestedBlock +// and all fields are equal. +func (b SingleNestedBlock) Equal(o fwschema.Block) bool { + if _, ok := o.(SingleNestedBlock); !ok { + return false + } + + return fwschema.BlocksEqual(b, o) +} + +// GetDeprecationMessage returns the DeprecationMessage field value. +func (b SingleNestedBlock) GetDeprecationMessage() string { + return b.DeprecationMessage +} + +// GetDescription returns the Description field value. +func (b SingleNestedBlock) GetDescription() string { + return b.Description +} + +// GetMarkdownDescription returns the MarkdownDescription field value. +func (b SingleNestedBlock) GetMarkdownDescription() string { + return b.MarkdownDescription +} + +// GetNestedObject returns a generated NestedBlockObject from the +// Attributes, CustomType, and Validators field values. +func (b SingleNestedBlock) GetNestedObject() fwschema.NestedBlockObject { + return NestedBlockObject{ + Attributes: b.Attributes, + Blocks: b.Blocks, + CustomType: b.CustomType, + } +} + +// GetNestingMode always returns BlockNestingModeSingle. +func (b SingleNestedBlock) GetNestingMode() fwschema.BlockNestingMode { + return fwschema.BlockNestingModeSingle +} + +// Type returns ObjectType or CustomType. +func (b SingleNestedBlock) Type() attr.Type { + if b.CustomType != nil { + return b.CustomType + } + + attrTypes := make(map[string]attr.Type, len(b.Attributes)+len(b.Blocks)) + + for name, attribute := range b.Attributes { + attrTypes[name] = attribute.GetType() + } + + for name, block := range b.Blocks { + attrTypes[name] = block.Type() + } + + return types.ObjectType{ + AttrTypes: attrTypes, + } +} diff --git a/action/schema/single_nested_block_test.go b/action/schema/single_nested_block_test.go new file mode 100644 index 000000000..a52addce1 --- /dev/null +++ b/action/schema/single_nested_block_test.go @@ -0,0 +1,433 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema_test + +import ( + "fmt" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/action/schema" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" + "github.com/hashicorp/terraform-plugin-framework/internal/testing/testschema" + "github.com/hashicorp/terraform-plugin-framework/internal/testing/testtypes" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +func TestSingleNestedBlockApplyTerraform5AttributePathStep(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + block schema.SingleNestedBlock + step tftypes.AttributePathStep + expected any + expectedError error + }{ + "AttributeName-attribute": { + block: schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + step: tftypes.AttributeName("testattr"), + expected: schema.StringAttribute{}, + expectedError: nil, + }, + "AttributeName-block": { + block: schema.SingleNestedBlock{ + Blocks: map[string]schema.Block{ + "testblock": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + }, + }, + step: tftypes.AttributeName("testblock"), + expected: schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + expectedError: nil, + }, + "AttributeName-missing": { + block: schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + step: tftypes.AttributeName("other"), + expected: nil, + expectedError: fmt.Errorf("no attribute or block \"other\" on SingleNestedBlock"), + }, + "ElementKeyInt": { + block: schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + step: tftypes.ElementKeyInt(1), + expected: nil, + expectedError: fmt.Errorf("cannot apply step tftypes.ElementKeyInt to SingleNestedBlock"), + }, + "ElementKeyString": { + block: schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + step: tftypes.ElementKeyString("test"), + expected: nil, + expectedError: fmt.Errorf("cannot apply step tftypes.ElementKeyString to SingleNestedBlock"), + }, + "ElementKeyValue": { + block: schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + step: tftypes.ElementKeyValue(tftypes.NewValue(tftypes.String, "test")), + expected: nil, + expectedError: fmt.Errorf("cannot apply step tftypes.ElementKeyValue to SingleNestedBlock"), + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got, err := testCase.block.ApplyTerraform5AttributePathStep(testCase.step) + + if err != nil { + if testCase.expectedError == nil { + t.Fatalf("expected no error, got: %s", err) + } + + if !strings.Contains(err.Error(), testCase.expectedError.Error()) { + t.Fatalf("expected error %q, got: %s", testCase.expectedError, err) + } + } + + if err == nil && testCase.expectedError != nil { + t.Fatalf("got no error, expected: %s", testCase.expectedError) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestSingleNestedBlockGetDeprecationMessage(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + block schema.SingleNestedBlock + expected string + }{ + "no-deprecation-message": { + block: schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + expected: "", + }, + "deprecation-message": { + block: schema.SingleNestedBlock{ + DeprecationMessage: "test deprecation message", + }, + expected: "test deprecation message", + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.block.GetDeprecationMessage() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestSingleNestedBlockEqual(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + block schema.SingleNestedBlock + other fwschema.Block + expected bool + }{ + "different-type": { + block: schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + other: testschema.BlockWithObjectValidators{}, + expected: false, + }, + "different-attributes-definitions": { + block: schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{ + Optional: true, + }, + }, + }, + other: schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{ + Required: true, + }, + }, + }, + expected: false, + }, + "different-attributes-types": { + block: schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + other: schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.BoolAttribute{}, + }, + }, + expected: false, + }, + "different-blocks-definitions": { + block: schema.SingleNestedBlock{ + Blocks: map[string]schema.Block{ + "testblock": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{ + Optional: true, + }, + }, + }, + }, + }, + other: schema.SingleNestedBlock{ + Blocks: map[string]schema.Block{ + "testblock": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{ + Required: true, + }, + }, + }, + }, + }, + expected: false, + }, + "equal": { + block: schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + other: schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + expected: true, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.block.Equal(testCase.other) + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestSingleNestedBlockGetDescription(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + block schema.SingleNestedBlock + expected string + }{ + "no-description": { + block: schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + expected: "", + }, + "description": { + block: schema.SingleNestedBlock{ + Description: "test description", + }, + expected: "test description", + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.block.GetDescription() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestSingleNestedBlockGetMarkdownDescription(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + block schema.SingleNestedBlock + expected string + }{ + "no-markdown-description": { + block: schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + expected: "", + }, + "markdown-description": { + block: schema.SingleNestedBlock{ + MarkdownDescription: "test description", + }, + expected: "test description", + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.block.GetMarkdownDescription() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestSingleNestedBlockGetNestedObject(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + block schema.SingleNestedBlock + expected schema.NestedBlockObject + }{ + "nested-object": { + block: schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + Blocks: map[string]schema.Block{ + "testblock": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + }, + }, + expected: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + Blocks: map[string]schema.Block{ + "testblock": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + }, + }, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.block.GetNestedObject() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestSingleNestedBlockType(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + block schema.SingleNestedBlock + expected attr.Type + }{ + "base": { + block: schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + Blocks: map[string]schema.Block{ + "testblock": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + }, + }, + expected: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "testattr": types.StringType, + "testblock": types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "testattr": types.StringType, + }, + }, + }, + }, + }, + "custom-type": { + block: schema.SingleNestedBlock{ + CustomType: testtypes.ObjectType{}, + }, + expected: testtypes.ObjectType{}, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.block.Type() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} diff --git a/action/schema/string_attribute.go b/action/schema/string_attribute.go new file mode 100644 index 000000000..bbf03341a --- /dev/null +++ b/action/schema/string_attribute.go @@ -0,0 +1,169 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema + +import ( + "github.com/hashicorp/terraform-plugin-go/tftypes" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" +) + +// Ensure the implementation satisfies the desired interfaces. +var ( + _ Attribute = StringAttribute{} +) + +// StringAttribute represents a schema attribute that is a string. When +// retrieving the value for this attribute, use types.String as the value type +// unless the CustomType field is set. +// +// Terraform configurations configure this attribute using expressions that +// return a string or directly via double quote syntax. +// +// example_attribute = "value" +// +// Terraform configurations reference this attribute using the attribute name. +// +// .example_attribute +type StringAttribute struct { + // CustomType enables the use of a custom attribute type in place of the + // default basetypes.StringType. When retrieving data, the basetypes.StringValuable + // associated with this custom type must be used in place of types.String. + CustomType basetypes.StringTypable + + // Required indicates whether the practitioner must enter a value for + // this attribute or not. Required and Optional cannot both be true. + Required bool + + // Optional indicates whether the practitioner can choose to enter a value + // for this attribute or not. Optional and Required cannot both be true. + Optional bool + + // Description is used in various tooling, like the language server, to + // give practitioners more information about what this attribute is, + // what it's for, and how it should be used. It should be written as + // plain text, with no special formatting. + Description string + + // MarkdownDescription is used in various tooling, like the + // documentation generator, to give practitioners more information + // about what this attribute is, what it's for, and how it should be + // used. It should be formatted using Markdown. + MarkdownDescription string + + // DeprecationMessage defines warning diagnostic details to display when + // practitioner configurations use this Attribute. The warning diagnostic + // summary is automatically set to "Attribute Deprecated" along with + // configuration source file and line information. + // + // Set this field to a practitioner actionable message such as: + // + // - "Configure other_attribute instead. This attribute will be removed + // in the next major version of the provider." + // - "Remove this attribute's configuration as it no longer is used and + // the attribute will be removed in the next major version of the + // provider." + // + // In Terraform 1.2.7 and later, this warning diagnostic is displayed any + // time a practitioner attempts to configure a value for this attribute and + // certain scenarios where this attribute is referenced. + // + // In Terraform 1.2.6 and earlier, this warning diagnostic is only + // displayed when the Attribute is Required or Optional, and if the + // practitioner configuration sets the value to a known or unknown value + // (which may eventually be null). + // + // Across any Terraform version, there are no warnings raised for + // practitioner configuration values set directly to null, as there is no + // way for the framework to differentiate between an unset and null + // configuration due to how Terraform sends configuration information + // across the protocol. + // + // Additional information about deprecation enhancements for read-only + // attributes can be found in: + // + // - https://github.com/hashicorp/terraform/issues/7569 + // + DeprecationMessage string +} + +// ApplyTerraform5AttributePathStep always returns an error as it is not +// possible to step further into a StringAttribute. +func (a StringAttribute) ApplyTerraform5AttributePathStep(step tftypes.AttributePathStep) (interface{}, error) { + return a.GetType().ApplyTerraform5AttributePathStep(step) +} + +// Equal returns true if the given Attribute is a StringAttribute +// and all fields are equal. +func (a StringAttribute) Equal(o fwschema.Attribute) bool { + if _, ok := o.(StringAttribute); !ok { + return false + } + + return fwschema.AttributesEqual(a, o) +} + +// GetDeprecationMessage returns the DeprecationMessage field value. +func (a StringAttribute) GetDeprecationMessage() string { + return a.DeprecationMessage +} + +// GetDescription returns the Description field value. +func (a StringAttribute) GetDescription() string { + return a.Description +} + +// GetMarkdownDescription returns the MarkdownDescription field value. +func (a StringAttribute) GetMarkdownDescription() string { + return a.MarkdownDescription +} + +// GetType returns types.StringType or the CustomType field value if defined. +func (a StringAttribute) GetType() attr.Type { + if a.CustomType != nil { + return a.CustomType + } + + return types.StringType +} + +// IsComputed always returns false as action schema attributes cannot be Computed. +func (a StringAttribute) IsComputed() bool { + return false +} + +// IsOptional returns the Optional field value. +func (a StringAttribute) IsOptional() bool { + return a.Optional +} + +// IsRequired returns the Required field value. +func (a StringAttribute) IsRequired() bool { + return a.Required +} + +// IsSensitive always returns false as action schema attributes cannot be Sensitive. +func (a StringAttribute) IsSensitive() bool { + return false +} + +// IsWriteOnly always returns false as action schema attributes cannot be WriteOnly. +func (a StringAttribute) IsWriteOnly() bool { + return false +} + +// IsRequiredForImport returns false as this behavior is only relevant +// for managed resource identity schema attributes. +func (a StringAttribute) IsRequiredForImport() bool { + return false +} + +// IsOptionalForImport returns false as this behavior is only relevant +// for managed resource identity schema attributes. +func (a StringAttribute) IsOptionalForImport() bool { + return false +} diff --git a/action/schema/string_attribute_test.go b/action/schema/string_attribute_test.go new file mode 100644 index 000000000..bbfc3cfff --- /dev/null +++ b/action/schema/string_attribute_test.go @@ -0,0 +1,437 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema_test + +import ( + "fmt" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-go/tftypes" + + "github.com/hashicorp/terraform-plugin-framework/action/schema" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" + "github.com/hashicorp/terraform-plugin-framework/internal/testing/testschema" + "github.com/hashicorp/terraform-plugin-framework/internal/testing/testtypes" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +func TestStringAttributeApplyTerraform5AttributePathStep(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute schema.StringAttribute + step tftypes.AttributePathStep + expected any + expectedError error + }{ + "AttributeName": { + attribute: schema.StringAttribute{}, + step: tftypes.AttributeName("test"), + expected: nil, + expectedError: fmt.Errorf("cannot apply AttributePathStep tftypes.AttributeName to basetypes.StringType"), + }, + "ElementKeyInt": { + attribute: schema.StringAttribute{}, + step: tftypes.ElementKeyInt(1), + expected: nil, + expectedError: fmt.Errorf("cannot apply AttributePathStep tftypes.ElementKeyInt to basetypes.StringType"), + }, + "ElementKeyString": { + attribute: schema.StringAttribute{}, + step: tftypes.ElementKeyString("test"), + expected: nil, + expectedError: fmt.Errorf("cannot apply AttributePathStep tftypes.ElementKeyString to basetypes.StringType"), + }, + "ElementKeyValue": { + attribute: schema.StringAttribute{}, + step: tftypes.ElementKeyValue(tftypes.NewValue(tftypes.String, "test")), + expected: nil, + expectedError: fmt.Errorf("cannot apply AttributePathStep tftypes.ElementKeyValue to basetypes.StringType"), + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got, err := testCase.attribute.ApplyTerraform5AttributePathStep(testCase.step) + + if err != nil { + if testCase.expectedError == nil { + t.Fatalf("expected no error, got: %s", err) + } + + if !strings.Contains(err.Error(), testCase.expectedError.Error()) { + t.Fatalf("expected error %q, got: %s", testCase.expectedError, err) + } + } + + if err == nil && testCase.expectedError != nil { + t.Fatalf("got no error, expected: %s", testCase.expectedError) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestStringAttributeGetDeprecationMessage(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute schema.StringAttribute + expected string + }{ + "no-deprecation-message": { + attribute: schema.StringAttribute{}, + expected: "", + }, + "deprecation-message": { + attribute: schema.StringAttribute{ + DeprecationMessage: "test deprecation message", + }, + expected: "test deprecation message", + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.attribute.GetDeprecationMessage() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestStringAttributeEqual(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute schema.StringAttribute + other fwschema.Attribute + expected bool + }{ + "different-type": { + attribute: schema.StringAttribute{}, + other: testschema.AttributeWithStringValidators{}, + expected: false, + }, + "equal": { + attribute: schema.StringAttribute{}, + other: schema.StringAttribute{}, + expected: true, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.attribute.Equal(testCase.other) + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestStringAttributeGetDescription(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute schema.StringAttribute + expected string + }{ + "no-description": { + attribute: schema.StringAttribute{}, + expected: "", + }, + "description": { + attribute: schema.StringAttribute{ + Description: "test description", + }, + expected: "test description", + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.attribute.GetDescription() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestStringAttributeGetMarkdownDescription(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute schema.StringAttribute + expected string + }{ + "no-markdown-description": { + attribute: schema.StringAttribute{}, + expected: "", + }, + "markdown-description": { + attribute: schema.StringAttribute{ + MarkdownDescription: "test description", + }, + expected: "test description", + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.attribute.GetMarkdownDescription() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestStringAttributeGetType(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute schema.StringAttribute + expected attr.Type + }{ + "base": { + attribute: schema.StringAttribute{}, + expected: types.StringType, + }, + "custom-type": { + attribute: schema.StringAttribute{ + CustomType: testtypes.StringType{}, + }, + expected: testtypes.StringType{}, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.attribute.GetType() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestStringAttributeIsComputed(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute schema.StringAttribute + expected bool + }{ + "not-computed": { + attribute: schema.StringAttribute{}, + expected: false, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.attribute.IsComputed() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestStringAttributeIsOptional(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute schema.StringAttribute + expected bool + }{ + "not-optional": { + attribute: schema.StringAttribute{}, + expected: false, + }, + "optional": { + attribute: schema.StringAttribute{ + Optional: true, + }, + expected: true, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.attribute.IsOptional() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestStringAttributeIsRequired(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute schema.StringAttribute + expected bool + }{ + "not-required": { + attribute: schema.StringAttribute{}, + expected: false, + }, + "required": { + attribute: schema.StringAttribute{ + Required: true, + }, + expected: true, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.attribute.IsRequired() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestStringAttributeIsSensitive(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute schema.StringAttribute + expected bool + }{ + "not-sensitive": { + attribute: schema.StringAttribute{}, + expected: false, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.attribute.IsSensitive() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestStringAttributeIsWriteOnly(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute schema.StringAttribute + expected bool + }{ + "not-writeOnly": { + attribute: schema.StringAttribute{}, + expected: false, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.attribute.IsWriteOnly() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestStringAttributeIsRequiredForImport(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute schema.StringAttribute + expected bool + }{ + "not-requiredForImport": { + attribute: schema.StringAttribute{}, + expected: false, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.attribute.IsRequiredForImport() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestStringAttributeIsOptionalForImport(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + attribute schema.StringAttribute + expected bool + }{ + "not-optionalForImport": { + attribute: schema.StringAttribute{}, + expected: false, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.attribute.IsOptionalForImport() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} diff --git a/action/schema/unlinked_schema.go b/action/schema/unlinked_schema.go new file mode 100644 index 000000000..71af1a4f1 --- /dev/null +++ b/action/schema/unlinked_schema.go @@ -0,0 +1,164 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +var _ SchemaType = UnlinkedSchema{} + +// UnlinkedSchema defines the structure and value types of an unlinked action. An unlinked action +// cannot cause changes to resource state. +type UnlinkedSchema struct { + // Attributes is the mapping of underlying attribute names to attribute + // definitions. + // + // Names must only contain lowercase letters, numbers, and underscores. + // Names must not collide with any Blocks names. + Attributes map[string]Attribute + + // Blocks is the mapping of underlying block names to block definitions. + // + // Names must only contain lowercase letters, numbers, and underscores. + // Names must not collide with any Attributes names. + Blocks map[string]Block + + // Description is used in various tooling, like the language server, to + // give practitioners more information about what this action is, + // what it's for, and how it should be used. It should be written as + // plain text, with no special formatting. + Description string + + // MarkdownDescription is used in various tooling, like the + // documentation generator, to give practitioners more information + // about what this action is, what it's for, and how it should be + // used. It should be formatted using Markdown. + MarkdownDescription string + + // DeprecationMessage defines warning diagnostic details to display when + // practitioner configurations use this action. The warning diagnostic + // summary is automatically set to "Action Deprecated" along with + // configuration source file and line information. + // + // Set this field to a practitioner actionable message such as: + // + // - "Use examplecloud_do_thing action instead. This action + // will be removed in the next major version of the provider." + // - "Remove this action as it no longer is valid and + // will be removed in the next major version of the provider." + // + DeprecationMessage string +} + +func (s UnlinkedSchema) isActionSchemaType() {} + +// ApplyTerraform5AttributePathStep applies the given AttributePathStep to the +// schema. +func (s UnlinkedSchema) ApplyTerraform5AttributePathStep(step tftypes.AttributePathStep) (any, error) { + return fwschema.SchemaApplyTerraform5AttributePathStep(s, step) +} + +// AttributeAtPath returns the Attribute at the passed path. If the path points +// to an element or attribute of a complex type, rather than to an Attribute, +// it will return an ErrPathInsideAtomicAttribute error. +func (s UnlinkedSchema) AttributeAtPath(ctx context.Context, p path.Path) (fwschema.Attribute, diag.Diagnostics) { + return fwschema.SchemaAttributeAtPath(ctx, s, p) +} + +// AttributeAtPath returns the Attribute at the passed path. If the path points +// to an element or attribute of a complex type, rather than to an Attribute, +// it will return an ErrPathInsideAtomicAttribute error. +func (s UnlinkedSchema) AttributeAtTerraformPath(ctx context.Context, p *tftypes.AttributePath) (fwschema.Attribute, error) { + return fwschema.SchemaAttributeAtTerraformPath(ctx, s, p) +} + +// GetAttributes returns the Attributes field value. +func (s UnlinkedSchema) GetAttributes() map[string]fwschema.Attribute { + return schemaAttributes(s.Attributes) +} + +// GetBlocks returns the Blocks field value. +func (s UnlinkedSchema) GetBlocks() map[string]fwschema.Block { + return schemaBlocks(s.Blocks) +} + +// GetDeprecationMessage returns the DeprecationMessage field value. +func (s UnlinkedSchema) GetDeprecationMessage() string { + return s.DeprecationMessage +} + +// GetDescription returns the Description field value. +func (s UnlinkedSchema) GetDescription() string { + return s.Description +} + +// GetMarkdownDescription returns the MarkdownDescription field value. +func (s UnlinkedSchema) GetMarkdownDescription() string { + return s.MarkdownDescription +} + +// GetVersion always returns 0 as action schemas cannot be versioned. +func (s UnlinkedSchema) GetVersion() int64 { + return 0 +} + +// Type returns the framework type of the schema. +func (s UnlinkedSchema) Type() attr.Type { + return fwschema.SchemaType(s) +} + +// TypeAtPath returns the framework type at the given schema path. +func (s UnlinkedSchema) TypeAtPath(ctx context.Context, p path.Path) (attr.Type, diag.Diagnostics) { + return fwschema.SchemaTypeAtPath(ctx, s, p) +} + +// TypeAtTerraformPath returns the framework type at the given tftypes path. +func (s UnlinkedSchema) TypeAtTerraformPath(ctx context.Context, p *tftypes.AttributePath) (attr.Type, error) { + return fwschema.SchemaTypeAtTerraformPath(ctx, s, p) +} + +// ValidateImplementation contains logic for validating the provider-defined +// implementation of the schema and underlying attributes and blocks to prevent +// unexpected errors or panics. This logic runs during the GetProviderSchema RPC, +// or via provider-defined unit testing, and should never include false positives. +func (s UnlinkedSchema) ValidateImplementation(ctx context.Context) diag.Diagnostics { + var diags diag.Diagnostics + + for attributeName, attribute := range s.GetAttributes() { + req := fwschema.ValidateImplementationRequest{ + Name: attributeName, + Path: path.Root(attributeName), + } + + // TODO:Actions: We should confirm with core, but we should be able to remove this next line. + // + // Action schemas define a specific "config" nested block in the action block, which means there + // shouldn't be any conflict with existing or future Terraform core attributes. + diags.Append(fwschema.IsReservedResourceAttributeName(req.Name, req.Path)...) + diags.Append(fwschema.ValidateAttributeImplementation(ctx, attribute, req)...) + } + + for blockName, block := range s.GetBlocks() { + req := fwschema.ValidateImplementationRequest{ + Name: blockName, + Path: path.Root(blockName), + } + + // TODO:Actions: We should confirm with core, but we should be able to remove this next line. + // + // Action schemas define a specific "config" nested block in the action block, which means there + // shouldn't be any conflict with existing or future Terraform core attributes. + diags.Append(fwschema.IsReservedResourceAttributeName(req.Name, req.Path)...) + diags.Append(fwschema.ValidateBlockImplementation(ctx, block, req)...) + } + + return diags +} diff --git a/action/schema/unlinked_schema_test.go b/action/schema/unlinked_schema_test.go new file mode 100644 index 000000000..1af3164f7 --- /dev/null +++ b/action/schema/unlinked_schema_test.go @@ -0,0 +1,1250 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package schema_test + +import ( + "context" + "fmt" + "strings" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-go/tftypes" + + "github.com/hashicorp/terraform-plugin-framework/action/schema" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +func TestSchemaApplyTerraform5AttributePathStep(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + schema schema.UnlinkedSchema + step tftypes.AttributePathStep + expected any + expectedError error + }{ + "AttributeName-attribute": { + schema: schema.UnlinkedSchema{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + step: tftypes.AttributeName("testattr"), + expected: schema.StringAttribute{}, + expectedError: nil, + }, + "AttributeName-block": { + schema: schema.UnlinkedSchema{ + Blocks: map[string]schema.Block{ + "testblock": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + }, + }, + step: tftypes.AttributeName("testblock"), + expected: schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + expectedError: nil, + }, + "AttributeName-missing": { + schema: schema.UnlinkedSchema{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + step: tftypes.AttributeName("other"), + expected: nil, + expectedError: fmt.Errorf("could not find attribute or block \"other\" in schema"), + }, + "ElementKeyInt": { + schema: schema.UnlinkedSchema{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + step: tftypes.ElementKeyInt(1), + expected: nil, + expectedError: fmt.Errorf("cannot apply AttributePathStep tftypes.ElementKeyInt to schema"), + }, + "ElementKeyString": { + schema: schema.UnlinkedSchema{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + step: tftypes.ElementKeyString("test"), + expected: nil, + expectedError: fmt.Errorf("cannot apply AttributePathStep tftypes.ElementKeyString to schema"), + }, + "ElementKeyValue": { + schema: schema.UnlinkedSchema{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + step: tftypes.ElementKeyValue(tftypes.NewValue(tftypes.String, "test")), + expected: nil, + expectedError: fmt.Errorf("cannot apply AttributePathStep tftypes.ElementKeyValue to schema"), + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got, err := testCase.schema.ApplyTerraform5AttributePathStep(testCase.step) + + if err != nil { + if testCase.expectedError == nil { + t.Fatalf("expected no error, got: %s", err) + } + + if !strings.Contains(err.Error(), testCase.expectedError.Error()) { + t.Fatalf("expected error %q, got: %s", testCase.expectedError, err) + } + } + + if err == nil && testCase.expectedError != nil { + t.Fatalf("got no error, expected: %s", testCase.expectedError) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestSchemaAttributeAtPath(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + schema schema.UnlinkedSchema + path path.Path + expected fwschema.Attribute + expectedDiags diag.Diagnostics + }{ + "empty-root": { + schema: schema.UnlinkedSchema{}, + path: path.Empty(), + expected: nil, + expectedDiags: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + path.Empty(), + "Invalid Schema Path", + "When attempting to get the framework attribute associated with a schema path, an unexpected error was returned. "+ + "This is always an issue with the provider. Please report this to the provider developers.\n\n"+ + "Path: \n"+ + "Original Error: got unexpected type schema.UnlinkedSchema", + ), + }, + }, + "root": { + schema: schema.UnlinkedSchema{ + Attributes: map[string]schema.Attribute{ + "test": schema.StringAttribute{}, + }, + }, + path: path.Empty(), + expected: nil, + expectedDiags: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + path.Empty(), + "Invalid Schema Path", + "When attempting to get the framework attribute associated with a schema path, an unexpected error was returned. "+ + "This is always an issue with the provider. Please report this to the provider developers.\n\n"+ + "Path: \n"+ + "Original Error: got unexpected type schema.UnlinkedSchema", + ), + }, + }, + "WithAttributeName-attribute": { + schema: schema.UnlinkedSchema{ + Attributes: map[string]schema.Attribute{ + "other": schema.BoolAttribute{}, + "test": schema.StringAttribute{}, + }, + }, + path: path.Root("test"), + expected: schema.StringAttribute{}, + }, + "WithAttributeName-block": { + schema: schema.UnlinkedSchema{ + Blocks: map[string]schema.Block{ + "other": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "otherattr": schema.StringAttribute{}, + }, + }, + "test": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + }, + }, + path: path.Root("test"), + expected: nil, + expectedDiags: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + path.Root("test"), + "Invalid Schema Path", + "When attempting to get the framework attribute associated with a schema path, an unexpected error was returned. "+ + "This is always an issue with the provider. Please report this to the provider developers.\n\n"+ + "Path: test\n"+ + "Original Error: "+fwschema.ErrPathIsBlock.Error(), + ), + }, + }, + "WithElementKeyInt": { + schema: schema.UnlinkedSchema{ + Attributes: map[string]schema.Attribute{ + "test": schema.StringAttribute{}, + }, + }, + path: path.Empty().AtListIndex(0), + expected: nil, + expectedDiags: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + path.Empty().AtListIndex(0), + "Invalid Schema Path", + "When attempting to get the framework attribute associated with a schema path, an unexpected error was returned. "+ + "This is always an issue with the provider. Please report this to the provider developers.\n\n"+ + "Path: [0]\n"+ + "Original Error: ElementKeyInt(0) still remains in the path: cannot apply AttributePathStep tftypes.ElementKeyInt to schema", + ), + }, + }, + "WithElementKeyString": { + schema: schema.UnlinkedSchema{ + Attributes: map[string]schema.Attribute{ + "test": schema.StringAttribute{}, + }, + }, + path: path.Empty().AtMapKey("test"), + expected: nil, + expectedDiags: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + path.Empty().AtMapKey("test"), + "Invalid Schema Path", + "When attempting to get the framework attribute associated with a schema path, an unexpected error was returned. "+ + "This is always an issue with the provider. Please report this to the provider developers.\n\n"+ + "Path: [\"test\"]\n"+ + "Original Error: ElementKeyString(\"test\") still remains in the path: cannot apply AttributePathStep tftypes.ElementKeyString to schema", + ), + }, + }, + "WithElementKeyValue": { + schema: schema.UnlinkedSchema{ + Attributes: map[string]schema.Attribute{ + "test": schema.StringAttribute{}, + }, + }, + path: path.Empty().AtSetValue(types.StringValue("test")), + expected: nil, + expectedDiags: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + path.Empty().AtSetValue(types.StringValue("test")), + "Invalid Schema Path", + "When attempting to get the framework attribute associated with a schema path, an unexpected error was returned. "+ + "This is always an issue with the provider. Please report this to the provider developers.\n\n"+ + "Path: [Value(\"test\")]\n"+ + "Original Error: ElementKeyValue(tftypes.String<\"test\">) still remains in the path: cannot apply AttributePathStep tftypes.ElementKeyValue to schema", + ), + }, + }, + } + + for name, tc := range testCases { + + t.Run(name, func(t *testing.T) { + t.Parallel() + + got, diags := tc.schema.AttributeAtPath(context.Background(), tc.path) + + if diff := cmp.Diff(diags, tc.expectedDiags); diff != "" { + t.Errorf("Unexpected diagnostics (+wanted, -got): %s", diff) + } + + if diff := cmp.Diff(got, tc.expected); diff != "" { + t.Errorf("Unexpected result (+wanted, -got): %s", diff) + } + }) + } +} + +func TestSchemaAttributeAtTerraformPath(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + schema schema.UnlinkedSchema + path *tftypes.AttributePath + expected fwschema.Attribute + expectedErr string + }{ + "empty-root": { + schema: schema.UnlinkedSchema{}, + path: tftypes.NewAttributePath(), + expected: nil, + expectedErr: "got unexpected type schema.UnlinkedSchema", + }, + "empty-nil": { + schema: schema.UnlinkedSchema{}, + path: nil, + expected: nil, + expectedErr: "got unexpected type schema.UnlinkedSchema", + }, + "root": { + schema: schema.UnlinkedSchema{ + Attributes: map[string]schema.Attribute{ + "test": schema.StringAttribute{}, + }, + }, + path: tftypes.NewAttributePath(), + expected: nil, + expectedErr: "got unexpected type schema.UnlinkedSchema", + }, + "nil": { + schema: schema.UnlinkedSchema{ + Attributes: map[string]schema.Attribute{ + "test": schema.StringAttribute{}, + }, + }, + path: nil, + expected: nil, + expectedErr: "got unexpected type schema.UnlinkedSchema", + }, + "WithAttributeName-attribute": { + schema: schema.UnlinkedSchema{ + Attributes: map[string]schema.Attribute{ + "other": schema.BoolAttribute{}, + "test": schema.StringAttribute{}, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test"), + expected: schema.StringAttribute{}, + }, + "WithAttributeName-block": { + schema: schema.UnlinkedSchema{ + Blocks: map[string]schema.Block{ + "other": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "otherattr": schema.StringAttribute{}, + }, + }, + "test": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("test"), + expected: nil, + expectedErr: fwschema.ErrPathIsBlock.Error(), + }, + "WithElementKeyInt": { + schema: schema.UnlinkedSchema{ + Attributes: map[string]schema.Attribute{ + "test": schema.StringAttribute{}, + }, + }, + path: tftypes.NewAttributePath().WithElementKeyInt(0), + expected: nil, + expectedErr: "ElementKeyInt(0) still remains in the path: cannot apply AttributePathStep tftypes.ElementKeyInt to schema", + }, + "WithElementKeyString": { + schema: schema.UnlinkedSchema{ + Attributes: map[string]schema.Attribute{ + "test": schema.StringAttribute{}, + }, + }, + path: tftypes.NewAttributePath().WithElementKeyString("test"), + expected: nil, + expectedErr: "ElementKeyString(\"test\") still remains in the path: cannot apply AttributePathStep tftypes.ElementKeyString to schema", + }, + "WithElementKeyValue": { + schema: schema.UnlinkedSchema{ + Attributes: map[string]schema.Attribute{ + "test": schema.StringAttribute{}, + }, + }, + path: tftypes.NewAttributePath().WithElementKeyValue(tftypes.NewValue(tftypes.String, "test")), + expected: nil, + expectedErr: "ElementKeyValue(tftypes.String<\"test\">) still remains in the path: cannot apply AttributePathStep tftypes.ElementKeyValue to schema", + }, + } + + for name, tc := range testCases { + + t.Run(name, func(t *testing.T) { + t.Parallel() + + got, err := tc.schema.AttributeAtTerraformPath(context.Background(), tc.path) + + 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 err == nil && 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 result (+wanted, -got): %s", diff) + } + }) + } +} + +func TestSchemaGetAttributes(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + schema schema.UnlinkedSchema + expected map[string]fwschema.Attribute + }{ + "no-attributes": { + schema: schema.UnlinkedSchema{}, + expected: map[string]fwschema.Attribute{}, + }, + "attributes": { + schema: schema.UnlinkedSchema{ + Attributes: map[string]schema.Attribute{ + "testattr1": schema.StringAttribute{}, + "testattr2": schema.StringAttribute{}, + }, + }, + expected: map[string]fwschema.Attribute{ + "testattr1": schema.StringAttribute{}, + "testattr2": schema.StringAttribute{}, + }, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.schema.GetAttributes() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestSchemaGetBlocks(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + schema schema.UnlinkedSchema + expected map[string]fwschema.Block + }{ + "no-blocks": { + schema: schema.UnlinkedSchema{}, + expected: map[string]fwschema.Block{}, + }, + "blocks": { + schema: schema.UnlinkedSchema{ + Blocks: map[string]schema.Block{ + "testblock1": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + "testblock2": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + }, + }, + expected: map[string]fwschema.Block{ + "testblock1": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + "testblock2": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + }, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.schema.GetBlocks() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestSchemaGetDeprecationMessage(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + schema schema.UnlinkedSchema + expected string + }{ + "no-deprecation-message": { + schema: schema.UnlinkedSchema{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + expected: "", + }, + "deprecation-message": { + schema: schema.UnlinkedSchema{ + DeprecationMessage: "test deprecation message", + }, + expected: "test deprecation message", + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.schema.GetDeprecationMessage() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestSchemaGetDescription(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + schema schema.UnlinkedSchema + expected string + }{ + "no-description": { + schema: schema.UnlinkedSchema{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + expected: "", + }, + "description": { + schema: schema.UnlinkedSchema{ + Description: "test description", + }, + expected: "test description", + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.schema.GetDescription() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestSchemaGetMarkdownDescription(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + schema schema.UnlinkedSchema + expected string + }{ + "no-markdown-description": { + schema: schema.UnlinkedSchema{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + expected: "", + }, + "markdown-description": { + schema: schema.UnlinkedSchema{ + MarkdownDescription: "test description", + }, + expected: "test description", + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.schema.GetMarkdownDescription() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestSchemaGetVersion(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + schema schema.UnlinkedSchema + expected int64 + }{ + "no-version": { + schema: schema.UnlinkedSchema{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + expected: 0, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.schema.GetVersion() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestSchemaType(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + schema schema.UnlinkedSchema + expected attr.Type + }{ + "base": { + schema: schema.UnlinkedSchema{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + Blocks: map[string]schema.Block{ + "testblock": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "testattr": schema.StringAttribute{}, + }, + }, + }, + }, + expected: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "testattr": types.StringType, + "testblock": types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "testattr": types.StringType, + }, + }, + }, + }, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.schema.Type() + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestSchemaTypeAtPath(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + schema schema.UnlinkedSchema + path path.Path + expected attr.Type + expectedDiags diag.Diagnostics + }{ + "empty-schema-empty-path": { + schema: schema.UnlinkedSchema{}, + path: path.Empty(), + expected: types.ObjectType{}, + }, + "empty-path": { + schema: schema.UnlinkedSchema{ + Attributes: map[string]schema.Attribute{ + "bool": schema.BoolAttribute{}, + "string": schema.StringAttribute{}, + }, + }, + path: path.Empty(), + expected: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "bool": types.BoolType, + "string": types.StringType, + }, + }, + }, + "AttributeName-Attribute": { + schema: schema.UnlinkedSchema{ + Attributes: map[string]schema.Attribute{ + "bool": schema.BoolAttribute{}, + "string": schema.StringAttribute{}, + }, + }, + path: path.Root("string"), + expected: types.StringType, + }, + "AttributeName-Block": { + schema: schema.UnlinkedSchema{ + Blocks: map[string]schema.Block{ + "single_block": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "single_block_nested": schema.StringAttribute{}, + }, + }, + }, + }, + path: path.Root("single_block"), + expected: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "single_block_nested": types.StringType, + }, + }, + }, + "AttributeName-non-existent": { + schema: schema.UnlinkedSchema{}, + path: path.Root("non-existent"), + expectedDiags: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + path.Root("non-existent"), + "Invalid Schema Path", + "When attempting to get the framework type associated with a schema path, an unexpected error was returned. This is always an issue with the provider. Please report this to the provider developers.\n\n"+ + "Path: non-existent\n"+ + "Original Error: AttributeName(\"non-existent\") still remains in the path: could not find attribute or block \"non-existent\" in schema", + ), + }, + }, + "ElementKeyInt": { + schema: schema.UnlinkedSchema{}, + path: path.Empty().AtListIndex(0), + expectedDiags: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + path.Empty().AtListIndex(0), + "Invalid Schema Path", + "When attempting to get the framework type associated with a schema path, an unexpected error was returned. This is always an issue with the provider. Please report this to the provider developers.\n\n"+ + "Path: [0]\n"+ + "Original Error: ElementKeyInt(0) still remains in the path: cannot apply AttributePathStep tftypes.ElementKeyInt to schema", + ), + }, + }, + "ElementKeyString": { + schema: schema.UnlinkedSchema{}, + path: path.Empty().AtMapKey("invalid"), + expectedDiags: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + path.Empty().AtMapKey("invalid"), + "Invalid Schema Path", + "When attempting to get the framework type associated with a schema path, an unexpected error was returned. This is always an issue with the provider. Please report this to the provider developers.\n\n"+ + "Path: [\"invalid\"]\n"+ + "Original Error: ElementKeyString(\"invalid\") still remains in the path: cannot apply AttributePathStep tftypes.ElementKeyString to schema", + ), + }, + }, + "ElementKeyValue": { + schema: schema.UnlinkedSchema{}, + path: path.Empty().AtSetValue(types.StringNull()), + expectedDiags: diag.Diagnostics{ + diag.NewAttributeErrorDiagnostic( + path.Empty().AtSetValue(types.StringNull()), + "Invalid Schema Path", + "When attempting to get the framework type associated with a schema path, an unexpected error was returned. This is always an issue with the provider. Please report this to the provider developers.\n\n"+ + "Path: [Value()]\n"+ + "Original Error: ElementKeyValue(tftypes.String) still remains in the path: cannot apply AttributePathStep tftypes.ElementKeyValue to schema", + ), + }, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got, diags := testCase.schema.TypeAtPath(context.Background(), testCase.path) + + if diff := cmp.Diff(diags, testCase.expectedDiags); diff != "" { + t.Errorf("unexpected diagnostics difference: %s", diff) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestSchemaTypeAtTerraformPath(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + schema schema.UnlinkedSchema + path *tftypes.AttributePath + expected attr.Type + expectedError error + }{ + "empty-schema-nil-path": { + schema: schema.UnlinkedSchema{}, + path: nil, + expected: types.ObjectType{}, + }, + "empty-schema-empty-path": { + schema: schema.UnlinkedSchema{}, + path: tftypes.NewAttributePath(), + expected: types.ObjectType{}, + }, + "nil-path": { + schema: schema.UnlinkedSchema{ + Attributes: map[string]schema.Attribute{ + "bool": schema.BoolAttribute{}, + "string": schema.StringAttribute{}, + }, + }, + path: nil, + expected: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "bool": types.BoolType, + "string": types.StringType, + }, + }, + }, + "empty-path": { + schema: schema.UnlinkedSchema{ + Attributes: map[string]schema.Attribute{ + "bool": schema.BoolAttribute{}, + "string": schema.StringAttribute{}, + }, + }, + path: tftypes.NewAttributePath(), + expected: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "bool": types.BoolType, + "string": types.StringType, + }, + }, + }, + "AttributeName-Attribute": { + schema: schema.UnlinkedSchema{ + Attributes: map[string]schema.Attribute{ + "bool": schema.BoolAttribute{}, + "string": schema.StringAttribute{}, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("string"), + expected: types.StringType, + }, + "AttributeName-Block": { + schema: schema.UnlinkedSchema{ + Blocks: map[string]schema.Block{ + "single_block": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "single_block_nested": schema.StringAttribute{}, + }, + }, + }, + }, + path: tftypes.NewAttributePath().WithAttributeName("single_block"), + expected: types.ObjectType{ + AttrTypes: map[string]attr.Type{ + "single_block_nested": types.StringType, + }, + }, + }, + "AttributeName-non-existent": { + schema: schema.UnlinkedSchema{}, + path: tftypes.NewAttributePath().WithAttributeName("non-existent"), + expectedError: fmt.Errorf("AttributeName(\"non-existent\") still remains in the path: could not find attribute or block \"non-existent\" in schema"), + }, + "ElementKeyInt": { + schema: schema.UnlinkedSchema{}, + path: tftypes.NewAttributePath().WithElementKeyInt(0), + expectedError: fmt.Errorf("ElementKeyInt(0) still remains in the path: cannot apply AttributePathStep tftypes.ElementKeyInt to schema"), + }, + "ElementKeyString": { + schema: schema.UnlinkedSchema{}, + path: tftypes.NewAttributePath().WithElementKeyString("invalid"), + expectedError: fmt.Errorf("ElementKeyString(\"invalid\") still remains in the path: cannot apply AttributePathStep tftypes.ElementKeyString to schema"), + }, + "ElementKeyValue": { + schema: schema.UnlinkedSchema{}, + path: tftypes.NewAttributePath().WithElementKeyValue(tftypes.NewValue(tftypes.String, nil)), + expectedError: fmt.Errorf("ElementKeyValue(tftypes.String) still remains in the path: cannot apply AttributePathStep tftypes.ElementKeyValue to schema"), + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got, err := testCase.schema.TypeAtTerraformPath(context.Background(), testCase.path) + + if err != nil { + if testCase.expectedError == nil { + t.Fatalf("expected no error, got: %s", err) + } + + if !strings.Contains(err.Error(), testCase.expectedError.Error()) { + t.Fatalf("expected error %q, got: %s", testCase.expectedError, err) + } + } + + if err == nil && testCase.expectedError != nil { + t.Fatalf("got no error, expected: %s", testCase.expectedError) + } + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestSchemaValidateImplementation(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + schema schema.UnlinkedSchema + expectedDiags diag.Diagnostics + }{ + "empty-schema": { + schema: schema.UnlinkedSchema{}, + }, + "attribute-using-reserved-field-name": { + schema: schema.UnlinkedSchema{ + Attributes: map[string]schema.Attribute{ + "depends_on": schema.StringAttribute{}, + }, + }, + expectedDiags: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Reserved Root Attribute/Block Name", + "When validating the resource or data source schema, an implementation issue was found. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + "\"depends_on\" is a reserved root attribute/block name. "+ + "This is to prevent practitioners from needing special Terraform configuration syntax.", + ), + }, + }, + "block-using-reserved-field-name": { + schema: schema.UnlinkedSchema{ + Blocks: map[string]schema.Block{ + "connection": schema.SingleNestedBlock{}, + }, + }, + expectedDiags: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Reserved Root Attribute/Block Name", + "When validating the resource or data source schema, an implementation issue was found. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + "\"connection\" is a reserved root attribute/block name. "+ + "This is to prevent practitioners from needing special Terraform configuration syntax.", + ), + }, + }, + "nested-attribute-using-nested-reserved-field-name": { + schema: schema.UnlinkedSchema{ + Attributes: map[string]schema.Attribute{ + "single_nested_attribute": schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "depends_on": schema.BoolAttribute{}, + }, + }, + }, + }, + }, + "nested-block-using-nested-reserved-field-name": { + schema: schema.UnlinkedSchema{ + Blocks: map[string]schema.Block{ + "single_nested_block": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "connection": schema.BoolAttribute{}, + }, + }, + }, + }, + }, + "attribute-and-blocks-using-reserved-field-names": { + schema: schema.UnlinkedSchema{ + Attributes: map[string]schema.Attribute{ + "depends_on": schema.StringAttribute{}, + }, + Blocks: map[string]schema.Block{ + "connection": schema.SingleNestedBlock{}, + }, + }, + expectedDiags: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Reserved Root Attribute/Block Name", + "When validating the resource or data source schema, an implementation issue was found. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + "\"depends_on\" is a reserved root attribute/block name. "+ + "This is to prevent practitioners from needing special Terraform configuration syntax.", + ), + diag.NewErrorDiagnostic( + "Reserved Root Attribute/Block Name", + "When validating the resource or data source schema, an implementation issue was found. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + "\"connection\" is a reserved root attribute/block name. "+ + "This is to prevent practitioners from needing special Terraform configuration syntax.", + ), + }, + }, + "attribute-using-invalid-field-name": { + schema: schema.UnlinkedSchema{ + Attributes: map[string]schema.Attribute{ + "^": schema.StringAttribute{}, + }, + }, + expectedDiags: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Invalid Attribute/Block Name", + "When validating the schema, an implementation issue was found. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + "\"^\" at schema path \"^\" is an invalid attribute/block name. "+ + "Names must only contain lowercase alphanumeric characters (a-z, 0-9) and underscores (_).", + ), + }, + }, + "block-using-invalid-field-name": { + schema: schema.UnlinkedSchema{ + Blocks: map[string]schema.Block{ + "^": schema.SingleNestedBlock{}, + }, + }, + expectedDiags: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Invalid Attribute/Block Name", + "When validating the schema, an implementation issue was found. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + "\"^\" at schema path \"^\" is an invalid attribute/block name. "+ + "Names must only contain lowercase alphanumeric characters (a-z, 0-9) and underscores (_).", + ), + }, + }, + "nested-attribute-using-nested-invalid-field-name": { + schema: schema.UnlinkedSchema{ + Attributes: map[string]schema.Attribute{ + "single_nested_attribute": schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "^": schema.BoolAttribute{}, + }, + }, + }, + }, + expectedDiags: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Invalid Attribute/Block Name", + "When validating the schema, an implementation issue was found. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + "\"^\" at schema path \"single_nested_attribute.^\" is an invalid attribute/block name. "+ + "Names must only contain lowercase alphanumeric characters (a-z, 0-9) and underscores (_).", + ), + }, + }, + "nested-block-using-nested-invalid-field-name": { + schema: schema.UnlinkedSchema{ + Blocks: map[string]schema.Block{ + "single_nested_block": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "^": schema.BoolAttribute{}, + }, + }, + }, + }, + expectedDiags: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Invalid Attribute/Block Name", + "When validating the schema, an implementation issue was found. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + "\"^\" at schema path \"single_nested_block.^\" is an invalid attribute/block name. "+ + "Names must only contain lowercase alphanumeric characters (a-z, 0-9) and underscores (_).", + ), + }, + }, + "nested-block-with-nested-block-using-invalid-field-names": { + schema: schema.UnlinkedSchema{ + Blocks: map[string]schema.Block{ + "$": schema.SingleNestedBlock{ + Blocks: map[string]schema.Block{ + "^": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "!": schema.BoolAttribute{}, + }, + }, + }, + }, + }, + }, + expectedDiags: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Invalid Attribute/Block Name", + "When validating the schema, an implementation issue was found. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + "\"$\" at schema path \"$\" is an invalid attribute/block name. "+ + "Names must only contain lowercase alphanumeric characters (a-z, 0-9) and underscores (_).", + ), + diag.NewErrorDiagnostic( + "Invalid Attribute/Block Name", + "When validating the schema, an implementation issue was found. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + "\"^\" at schema path \"$.^\" is an invalid attribute/block name. "+ + "Names must only contain lowercase alphanumeric characters (a-z, 0-9) and underscores (_).", + ), + diag.NewErrorDiagnostic( + "Invalid Attribute/Block Name", + "When validating the schema, an implementation issue was found. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + "\"!\" at schema path \"$.^.!\" is an invalid attribute/block name. "+ + "Names must only contain lowercase alphanumeric characters (a-z, 0-9) and underscores (_).", + ), + }, + }, + "attribute-with-validate-attribute-implementation-error": { + schema: schema.UnlinkedSchema{ + Attributes: map[string]schema.Attribute{ + "test": schema.ListAttribute{ + Required: true, + }, + }, + }, + expectedDiags: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Invalid Attribute Implementation", + "When validating the schema, an implementation issue was found. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + "\"test\" is missing the CustomType or ElementType field on a collection Attribute. "+ + "One of these fields is required to prevent other unexpected errors or panics.", + ), + }, + }, + "nested-attribute-with-validate-attribute-implementation-error": { + schema: schema.UnlinkedSchema{ + Attributes: map[string]schema.Attribute{ + "single_nested_attribute": schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "test": schema.ListAttribute{ + Required: true, + }, + }, + }, + }, + }, + expectedDiags: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Invalid Attribute Implementation", + "When validating the schema, an implementation issue was found. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + "\"single_nested_attribute.test\" is missing the CustomType or ElementType field on a collection Attribute. "+ + "One of these fields is required to prevent other unexpected errors or panics.", + ), + }, + }, + "nested-block-attribute-with-validate-attribute-implementation-error": { + schema: schema.UnlinkedSchema{ + Blocks: map[string]schema.Block{ + "single_nested_block": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "test": schema.ListAttribute{ + Required: true, + }, + }, + }, + }, + }, + expectedDiags: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Invalid Attribute Implementation", + "When validating the schema, an implementation issue was found. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + "\"single_nested_block.test\" is missing the CustomType or ElementType field on a collection Attribute. "+ + "One of these fields is required to prevent other unexpected errors or panics.", + ), + }, + }, + "nested-nested-block-attribute-with-validate-attribute-implementation-error": { + schema: schema.UnlinkedSchema{ + Blocks: map[string]schema.Block{ + "single_nested_block": schema.SingleNestedBlock{ + Blocks: map[string]schema.Block{ + "single_nested_nested_block": schema.SingleNestedBlock{ + Attributes: map[string]schema.Attribute{ + "test": schema.ListAttribute{ + Required: true, + }, + }, + }, + }, + }, + }, + }, + expectedDiags: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Invalid Attribute Implementation", + "When validating the schema, an implementation issue was found. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + "\"single_nested_block.single_nested_nested_block.test\" is missing the CustomType or ElementType field on a collection Attribute. "+ + "One of these fields is required to prevent other unexpected errors or panics.", + ), + }, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + diags := testCase.schema.ValidateImplementation(context.Background()) + + if diff := cmp.Diff(diags, testCase.expectedDiags); diff != "" { + t.Errorf("Unexpected diagnostics (+wanted, -got): %s", diff) + } + }) + } +} From 0e5df9cca30cd61fc5cea206704acf738d1da501 Mon Sep 17 00:00:00 2001 From: Austin Valle Date: Thu, 10 Jul 2025 16:37:34 -0400 Subject: [PATCH 6/8] implement unlinked schemas, some attributes, and the rpcs --- action/schema.go | 15 +- action/schema/schema_type.go | 17 +- internal/fwserver/server.go | 3 +- internal/fwserver/server_actions.go | 61 +++- internal/fwserver/server_getmetadata.go | 13 + internal/fwserver/server_getmetadata_test.go | 184 +++++++++++ internal/fwserver/server_getproviderschema.go | 9 + .../fwserver/server_getproviderschema_test.go | 311 ++++++++++++++++++ internal/testing/testprovider/action.go | 37 +++ internal/testing/testprovider/provider.go | 11 + internal/toproto5/action_schema.go | 42 +++ internal/toproto5/action_schema_test.go | 125 +++++++ internal/toproto5/actionmetadata.go | 19 ++ internal/toproto5/actionmetadata_test.go | 44 +++ internal/toproto5/getmetadata.go | 5 + internal/toproto5/getmetadata_test.go | 34 ++ internal/toproto5/getproviderschema.go | 15 + internal/toproto5/getproviderschema_test.go | 250 ++++++++++++++ internal/toproto6/action_schema.go | 42 +++ internal/toproto6/action_schema_test.go | 125 +++++++ internal/toproto6/actionmetadata.go | 19 ++ internal/toproto6/actionmetadata_test.go | 44 +++ internal/toproto6/getmetadata.go | 5 + internal/toproto6/getmetadata_test.go | 34 ++ internal/toproto6/getproviderschema.go | 15 + internal/toproto6/getproviderschema_test.go | 262 +++++++++++++++ 26 files changed, 1731 insertions(+), 10 deletions(-) create mode 100644 internal/testing/testprovider/action.go create mode 100644 internal/toproto5/action_schema.go create mode 100644 internal/toproto5/action_schema_test.go create mode 100644 internal/toproto5/actionmetadata.go create mode 100644 internal/toproto5/actionmetadata_test.go create mode 100644 internal/toproto6/action_schema.go create mode 100644 internal/toproto6/action_schema_test.go create mode 100644 internal/toproto6/actionmetadata.go create mode 100644 internal/toproto6/actionmetadata_test.go diff --git a/action/schema.go b/action/schema.go index 9eb52bf7b..d8bc1b72d 100644 --- a/action/schema.go +++ b/action/schema.go @@ -4,8 +4,8 @@ package action import ( + "github.com/hashicorp/terraform-plugin-framework/action/schema" "github.com/hashicorp/terraform-plugin-framework/diag" - "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" ) // SchemaRequest represents a request for the Action to return its schema. @@ -17,10 +17,15 @@ type SchemaRequest struct{} // response struct is supplied as an argument to the Action type Schema // method. type SchemaResponse struct { - // TODO:Actions: This will eventually be replaced by an interface defined in - // an "actions/schema" package. Schema implementations that will fulfill this - // interface will be unlinked, linked, or lifecycle. (also defined in the "actions/schema" package) - Schema fwschema.Schema + + // Schema is the schema of the action. + // + // There are three different types of actions, which define how a practitioner can trigger an action, + // as well as what effect the action can have on the state. + // - [schema.UnlinkedSchema] actions are actions that cannot cause changes to resource states. + // - [schema.LifecycleSchema] actions are actions that can cause changes to exactly one resource state. + // - [schema.LinkedSchema] actions are actions that can cause changes to one or more resource states. + Schema schema.SchemaType // Diagnostics report errors or warnings related to retrieving the action schema. // An empty slice indicates success, with no warnings or errors generated. diff --git a/action/schema/schema_type.go b/action/schema/schema_type.go index 04122adf9..f9c5d689a 100644 --- a/action/schema/schema_type.go +++ b/action/schema/schema_type.go @@ -3,7 +3,12 @@ package schema -import "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" +) // TODO:Actions: Implement lifecycle and linked schemas // @@ -20,6 +25,16 @@ import "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" type SchemaType interface { fwschema.Schema + // MAINTAINER NOTE: Action schemas are unique to other schema types in framework in that the + // exported methods all return a schema interface ([SchemaType]) rather than a schema struct, + // due to the multiple different types of action schema implementations. + // + // As a result, there are certain methods that all schema structs implement that aren't defined in + // the [fwschema.Schema] interface, such as the ValidateImplementation method. So we are adding that + // here to the action schema interface to avoid additional internal interfaces and unnecessary + // type assertions. + ValidateImplementation(context.Context) diag.Diagnostics + // Action schema types are statically defined in the protocol, so this // interface is not meant to be implemented outside of this package isActionSchemaType() diff --git a/internal/fwserver/server.go b/internal/fwserver/server.go index 3fef10f28..22e2c2eb2 100644 --- a/internal/fwserver/server.go +++ b/internal/fwserver/server.go @@ -9,6 +9,7 @@ import ( "sync" "github.com/hashicorp/terraform-plugin-framework/action" + actionschema "github.com/hashicorp/terraform-plugin-framework/action/schema" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/ephemeral" @@ -44,7 +45,7 @@ type Server struct { // actionSchemas is the cached Action Schemas for RPCs that need to // convert configuration data from the protocol. If not found, it will be // fetched from the Action.Schema() method. - actionSchemas map[string]fwschema.Schema + actionSchemas map[string]actionschema.SchemaType // actionSchemasMutex is a mutex to protect concurrent actionSchemas // access from race conditions. diff --git a/internal/fwserver/server_actions.go b/internal/fwserver/server_actions.go index 73f2b68a8..13d2e381f 100644 --- a/internal/fwserver/server_actions.go +++ b/internal/fwserver/server_actions.go @@ -8,8 +8,8 @@ import ( "fmt" "github.com/hashicorp/terraform-plugin-framework/action" + actionschema "github.com/hashicorp/terraform-plugin-framework/action/schema" "github.com/hashicorp/terraform-plugin-framework/diag" - "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" "github.com/hashicorp/terraform-plugin-framework/internal/logging" "github.com/hashicorp/terraform-plugin-framework/provider" ) @@ -94,9 +94,25 @@ func (s *Server) ActionFuncs(ctx context.Context) (map[string]func() action.Acti return s.actionFuncs, s.actionFuncsDiags } +// ActionMetadatas returns a slice of ActionMetadata for the GetMetadata +// RPC. +func (s *Server) ActionMetadatas(ctx context.Context) ([]ActionMetadata, diag.Diagnostics) { + actionFuncs, diags := s.ActionFuncs(ctx) + + actionMetadatas := make([]ActionMetadata, 0, len(actionFuncs)) + + for typeName := range actionFuncs { + actionMetadatas = append(actionMetadatas, ActionMetadata{ + TypeName: typeName, + }) + } + + return actionMetadatas, diags +} + // ActionSchema returns the Action Schema for the given type name and // caches the result for later Action operations. -func (s *Server) ActionSchema(ctx context.Context, actionType string) (fwschema.Schema, diag.Diagnostics) { +func (s *Server) ActionSchema(ctx context.Context, actionType string) (actionschema.SchemaType, diag.Diagnostics) { s.actionSchemasMutex.RLock() actionSchema, ok := s.actionSchemas[actionType] s.actionSchemasMutex.RUnlock() @@ -131,7 +147,7 @@ func (s *Server) ActionSchema(ctx context.Context, actionType string) (fwschema. s.actionSchemasMutex.Lock() if s.actionSchemas == nil { - s.actionSchemas = make(map[string]fwschema.Schema) + s.actionSchemas = make(map[string]actionschema.SchemaType) } s.actionSchemas[actionType] = schemaResp.Schema @@ -140,3 +156,42 @@ func (s *Server) ActionSchema(ctx context.Context, actionType string) (fwschema. return schemaResp.Schema, diags } + +// ActionSchemas returns a map of Action Schemas for the +// GetProviderSchema RPC without caching since not all schemas are guaranteed to +// be necessary for later provider operations. The schema implementations are +// also validated. +func (s *Server) ActionSchemas(ctx context.Context) (map[string]actionschema.SchemaType, diag.Diagnostics) { + actionSchemas := make(map[string]actionschema.SchemaType) + + actionFuncs, diags := s.ActionFuncs(ctx) + + for typeName, actionFunc := range actionFuncs { + actionImpl := actionFunc() + + schemaReq := action.SchemaRequest{} + schemaResp := action.SchemaResponse{} + + logging.FrameworkTrace(ctx, "Calling provider defined Action Schema", map[string]interface{}{logging.KeyActionType: typeName}) + actionImpl.Schema(ctx, schemaReq, &schemaResp) + logging.FrameworkTrace(ctx, "Called provider defined Action Schema", map[string]interface{}{logging.KeyActionType: typeName}) + + diags.Append(schemaResp.Diagnostics...) + + if schemaResp.Diagnostics.HasError() { + continue + } + + validateDiags := schemaResp.Schema.ValidateImplementation(ctx) + + diags.Append(validateDiags...) + + if validateDiags.HasError() { + continue + } + + actionSchemas[typeName] = schemaResp.Schema + } + + return actionSchemas, diags +} diff --git a/internal/fwserver/server_getmetadata.go b/internal/fwserver/server_getmetadata.go index 33b675865..ea75b7651 100644 --- a/internal/fwserver/server_getmetadata.go +++ b/internal/fwserver/server_getmetadata.go @@ -16,6 +16,7 @@ type GetMetadataRequest struct{} // GetMetadataResponse is the framework server response for the // GetMetadata RPC. type GetMetadataResponse struct { + Actions []ActionMetadata DataSources []DataSourceMetadata Diagnostics diag.Diagnostics EphemeralResources []EphemeralResourceMetadata @@ -60,8 +61,16 @@ type ListResourceMetadata struct { TypeName string } +// ActionMetadata is the framework server equivalent of the +// tfprotov5.ActionMetadata and tfprotov6.ActionMetadata types. +type ActionMetadata struct { + // TypeName is the name of the action. + TypeName string +} + // GetMetadata implements the framework server GetMetadata RPC. func (s *Server) GetMetadata(ctx context.Context, req *GetMetadataRequest, resp *GetMetadataResponse) { + resp.Actions = []ActionMetadata{} resp.DataSources = []DataSourceMetadata{} resp.EphemeralResources = []EphemeralResourceMetadata{} resp.Functions = []FunctionMetadata{} @@ -70,6 +79,9 @@ func (s *Server) GetMetadata(ctx context.Context, req *GetMetadataRequest, resp resp.ServerCapabilities = s.ServerCapabilities() + actionMetadatas, diags := s.ActionMetadatas(ctx) + resp.Diagnostics.Append(diags...) + datasourceMetadatas, diags := s.DataSourceMetadatas(ctx) resp.Diagnostics.Append(diags...) @@ -92,6 +104,7 @@ func (s *Server) GetMetadata(ctx context.Context, req *GetMetadataRequest, resp return } + resp.Actions = actionMetadatas resp.DataSources = datasourceMetadatas resp.EphemeralResources = ephemeralResourceMetadatas resp.Functions = functionMetadatas diff --git a/internal/fwserver/server_getmetadata_test.go b/internal/fwserver/server_getmetadata_test.go index dff3b7378..798a1a77a 100644 --- a/internal/fwserver/server_getmetadata_test.go +++ b/internal/fwserver/server_getmetadata_test.go @@ -11,6 +11,7 @@ import ( "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" + "github.com/hashicorp/terraform-plugin-framework/action" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/ephemeral" @@ -35,6 +36,170 @@ func TestServerGetMetadata(t *testing.T) { Provider: &testprovider.Provider{}, }, expectedResponse: &fwserver.GetMetadataResponse{ + Actions: []fwserver.ActionMetadata{}, + DataSources: []fwserver.DataSourceMetadata{}, + EphemeralResources: []fwserver.EphemeralResourceMetadata{}, + Functions: []fwserver.FunctionMetadata{}, + Resources: []fwserver.ResourceMetadata{}, + ServerCapabilities: &fwserver.ServerCapabilities{ + GetProviderSchemaOptional: true, + MoveResourceState: true, + PlanDestroy: true, + }, + }, + }, + "actions": { + server: &fwserver.Server{ + Provider: &testprovider.Provider{ + ActionsMethod: func(_ context.Context) []func() action.Action { + return []func() action.Action{ + func() action.Action { + return &testprovider.Action{ + MetadataMethod: func(_ context.Context, _ action.MetadataRequest, resp *action.MetadataResponse) { + resp.TypeName = "test_action1" + }, + } + }, + func() action.Action { + return &testprovider.Action{ + MetadataMethod: func(_ context.Context, _ action.MetadataRequest, resp *action.MetadataResponse) { + resp.TypeName = "test_action2" + }, + } + }, + } + }, + }, + }, + request: &fwserver.GetMetadataRequest{}, + expectedResponse: &fwserver.GetMetadataResponse{ + Actions: []fwserver.ActionMetadata{ + { + TypeName: "test_action1", + }, + { + TypeName: "test_action2", + }, + }, + DataSources: []fwserver.DataSourceMetadata{}, + EphemeralResources: []fwserver.EphemeralResourceMetadata{}, + Functions: []fwserver.FunctionMetadata{}, + Resources: []fwserver.ResourceMetadata{}, + ServerCapabilities: &fwserver.ServerCapabilities{ + GetProviderSchemaOptional: true, + MoveResourceState: true, + PlanDestroy: true, + }, + }, + }, + "actions-duplicate-type-name": { + server: &fwserver.Server{ + Provider: &testprovider.Provider{ + ActionsMethod: func(_ context.Context) []func() action.Action { + return []func() action.Action{ + func() action.Action { + return &testprovider.Action{ + MetadataMethod: func(_ context.Context, _ action.MetadataRequest, resp *action.MetadataResponse) { + resp.TypeName = "test_action" + }, + } + }, + func() action.Action { + return &testprovider.Action{ + MetadataMethod: func(_ context.Context, _ action.MetadataRequest, resp *action.MetadataResponse) { + resp.TypeName = "test_action" + }, + } + }, + } + }, + }, + }, + request: &fwserver.GetMetadataRequest{}, + expectedResponse: &fwserver.GetMetadataResponse{ + Actions: []fwserver.ActionMetadata{}, + DataSources: []fwserver.DataSourceMetadata{}, + EphemeralResources: []fwserver.EphemeralResourceMetadata{}, + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Duplicate Action Defined", + "The test_action action type was returned for multiple actions. "+ + "Action types must be unique. "+ + "This is always an issue with the provider and should be reported to the provider developers.", + ), + }, + Functions: []fwserver.FunctionMetadata{}, + Resources: []fwserver.ResourceMetadata{}, + ServerCapabilities: &fwserver.ServerCapabilities{ + GetProviderSchemaOptional: true, + MoveResourceState: true, + PlanDestroy: true, + }, + }, + }, + "actions-empty-type-name": { + server: &fwserver.Server{ + Provider: &testprovider.Provider{ + ActionsMethod: func(_ context.Context) []func() action.Action { + return []func() action.Action{ + func() action.Action { + return &testprovider.Action{ + MetadataMethod: func(_ context.Context, _ action.MetadataRequest, resp *action.MetadataResponse) { + resp.TypeName = "" + }, + } + }, + } + }, + }, + }, + request: &fwserver.GetMetadataRequest{}, + expectedResponse: &fwserver.GetMetadataResponse{ + Actions: []fwserver.ActionMetadata{}, + DataSources: []fwserver.DataSourceMetadata{}, + EphemeralResources: []fwserver.EphemeralResourceMetadata{}, + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Action Type Missing", + "The *testprovider.Action Action returned an empty string from the Metadata method. "+ + "This is always an issue with the provider and should be reported to the provider developers.", + ), + }, + Functions: []fwserver.FunctionMetadata{}, + Resources: []fwserver.ResourceMetadata{}, + ServerCapabilities: &fwserver.ServerCapabilities{ + GetProviderSchemaOptional: true, + MoveResourceState: true, + PlanDestroy: true, + }, + }, + }, + "actions-provider-type-name": { + server: &fwserver.Server{ + Provider: &testprovider.Provider{ + MetadataMethod: func(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) { + resp.TypeName = "testprovidertype" + }, + ActionsMethod: func(_ context.Context) []func() action.Action { + return []func() action.Action{ + func() action.Action { + return &testprovider.Action{ + MetadataMethod: func(_ context.Context, req action.MetadataRequest, resp *action.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_action" + }, + } + }, + } + }, + }, + }, + request: &fwserver.GetMetadataRequest{}, + expectedResponse: &fwserver.GetMetadataResponse{ + Actions: []fwserver.ActionMetadata{ + { + TypeName: "testprovidertype_action", + }, + }, DataSources: []fwserver.DataSourceMetadata{}, EphemeralResources: []fwserver.EphemeralResourceMetadata{}, Functions: []fwserver.FunctionMetadata{}, @@ -71,6 +236,7 @@ func TestServerGetMetadata(t *testing.T) { }, request: &fwserver.GetMetadataRequest{}, expectedResponse: &fwserver.GetMetadataResponse{ + Actions: []fwserver.ActionMetadata{}, DataSources: []fwserver.DataSourceMetadata{ { TypeName: "test_data_source1", @@ -114,6 +280,7 @@ func TestServerGetMetadata(t *testing.T) { }, request: &fwserver.GetMetadataRequest{}, expectedResponse: &fwserver.GetMetadataResponse{ + Actions: []fwserver.ActionMetadata{}, DataSources: []fwserver.DataSourceMetadata{}, EphemeralResources: []fwserver.EphemeralResourceMetadata{}, Diagnostics: diag.Diagnostics{ @@ -151,6 +318,7 @@ func TestServerGetMetadata(t *testing.T) { }, request: &fwserver.GetMetadataRequest{}, expectedResponse: &fwserver.GetMetadataResponse{ + Actions: []fwserver.ActionMetadata{}, DataSources: []fwserver.DataSourceMetadata{}, EphemeralResources: []fwserver.EphemeralResourceMetadata{}, Diagnostics: diag.Diagnostics{ @@ -190,6 +358,7 @@ func TestServerGetMetadata(t *testing.T) { }, request: &fwserver.GetMetadataRequest{}, expectedResponse: &fwserver.GetMetadataResponse{ + Actions: []fwserver.ActionMetadata{}, DataSources: []fwserver.DataSourceMetadata{ { TypeName: "testprovidertype_data_source", @@ -230,6 +399,7 @@ func TestServerGetMetadata(t *testing.T) { }, request: &fwserver.GetMetadataRequest{}, expectedResponse: &fwserver.GetMetadataResponse{ + Actions: []fwserver.ActionMetadata{}, DataSources: []fwserver.DataSourceMetadata{}, EphemeralResources: []fwserver.EphemeralResourceMetadata{ { @@ -273,6 +443,7 @@ func TestServerGetMetadata(t *testing.T) { }, request: &fwserver.GetMetadataRequest{}, expectedResponse: &fwserver.GetMetadataResponse{ + Actions: []fwserver.ActionMetadata{}, DataSources: []fwserver.DataSourceMetadata{}, EphemeralResources: []fwserver.EphemeralResourceMetadata{}, Diagnostics: diag.Diagnostics{ @@ -310,6 +481,7 @@ func TestServerGetMetadata(t *testing.T) { }, request: &fwserver.GetMetadataRequest{}, expectedResponse: &fwserver.GetMetadataResponse{ + Actions: []fwserver.ActionMetadata{}, DataSources: []fwserver.DataSourceMetadata{}, EphemeralResources: []fwserver.EphemeralResourceMetadata{}, Diagnostics: diag.Diagnostics{ @@ -349,6 +521,7 @@ func TestServerGetMetadata(t *testing.T) { }, request: &fwserver.GetMetadataRequest{}, expectedResponse: &fwserver.GetMetadataResponse{ + Actions: []fwserver.ActionMetadata{}, DataSources: []fwserver.DataSourceMetadata{}, EphemeralResources: []fwserver.EphemeralResourceMetadata{ { @@ -389,6 +562,7 @@ func TestServerGetMetadata(t *testing.T) { }, request: &fwserver.GetMetadataRequest{}, expectedResponse: &fwserver.GetMetadataResponse{ + Actions: []fwserver.ActionMetadata{}, DataSources: []fwserver.DataSourceMetadata{}, EphemeralResources: []fwserver.EphemeralResourceMetadata{}, Functions: []fwserver.FunctionMetadata{ @@ -432,6 +606,7 @@ func TestServerGetMetadata(t *testing.T) { }, request: &fwserver.GetMetadataRequest{}, expectedResponse: &fwserver.GetMetadataResponse{ + Actions: []fwserver.ActionMetadata{}, DataSources: []fwserver.DataSourceMetadata{}, EphemeralResources: []fwserver.EphemeralResourceMetadata{}, Diagnostics: diag.Diagnostics{ @@ -469,6 +644,7 @@ func TestServerGetMetadata(t *testing.T) { }, request: &fwserver.GetMetadataRequest{}, expectedResponse: &fwserver.GetMetadataResponse{ + Actions: []fwserver.ActionMetadata{}, DataSources: []fwserver.DataSourceMetadata{}, EphemeralResources: []fwserver.EphemeralResourceMetadata{}, Diagnostics: diag.Diagnostics{ @@ -516,6 +692,7 @@ func TestServerGetMetadata(t *testing.T) { }, request: &fwserver.GetMetadataRequest{}, expectedResponse: &fwserver.GetMetadataResponse{ + Actions: []fwserver.ActionMetadata{}, DataSources: []fwserver.DataSourceMetadata{}, EphemeralResources: []fwserver.EphemeralResourceMetadata{}, Diagnostics: diag.Diagnostics{}, @@ -551,6 +728,7 @@ func TestServerGetMetadata(t *testing.T) { }, request: &fwserver.GetMetadataRequest{}, expectedResponse: &fwserver.GetMetadataResponse{ + Actions: []fwserver.ActionMetadata{}, DataSources: []fwserver.DataSourceMetadata{}, EphemeralResources: []fwserver.EphemeralResourceMetadata{}, Diagnostics: diag.Diagnostics{ @@ -605,6 +783,7 @@ func TestServerGetMetadata(t *testing.T) { }, request: &fwserver.GetMetadataRequest{}, expectedResponse: &fwserver.GetMetadataResponse{ + Actions: []fwserver.ActionMetadata{}, DataSources: []fwserver.DataSourceMetadata{}, EphemeralResources: []fwserver.EphemeralResourceMetadata{}, Diagnostics: diag.Diagnostics{ @@ -653,6 +832,7 @@ func TestServerGetMetadata(t *testing.T) { }, request: &fwserver.GetMetadataRequest{}, expectedResponse: &fwserver.GetMetadataResponse{ + Actions: []fwserver.ActionMetadata{}, DataSources: []fwserver.DataSourceMetadata{}, EphemeralResources: []fwserver.EphemeralResourceMetadata{}, Diagnostics: diag.Diagnostics{ @@ -696,6 +876,7 @@ func TestServerGetMetadata(t *testing.T) { }, request: &fwserver.GetMetadataRequest{}, expectedResponse: &fwserver.GetMetadataResponse{ + Actions: []fwserver.ActionMetadata{}, DataSources: []fwserver.DataSourceMetadata{}, EphemeralResources: []fwserver.EphemeralResourceMetadata{}, Functions: []fwserver.FunctionMetadata{}, @@ -739,6 +920,7 @@ func TestServerGetMetadata(t *testing.T) { }, request: &fwserver.GetMetadataRequest{}, expectedResponse: &fwserver.GetMetadataResponse{ + Actions: []fwserver.ActionMetadata{}, DataSources: []fwserver.DataSourceMetadata{}, EphemeralResources: []fwserver.EphemeralResourceMetadata{}, Diagnostics: diag.Diagnostics{ @@ -776,6 +958,7 @@ func TestServerGetMetadata(t *testing.T) { }, request: &fwserver.GetMetadataRequest{}, expectedResponse: &fwserver.GetMetadataResponse{ + Actions: []fwserver.ActionMetadata{}, DataSources: []fwserver.DataSourceMetadata{}, EphemeralResources: []fwserver.EphemeralResourceMetadata{}, Diagnostics: diag.Diagnostics{ @@ -815,6 +998,7 @@ func TestServerGetMetadata(t *testing.T) { }, request: &fwserver.GetMetadataRequest{}, expectedResponse: &fwserver.GetMetadataResponse{ + Actions: []fwserver.ActionMetadata{}, DataSources: []fwserver.DataSourceMetadata{}, EphemeralResources: []fwserver.EphemeralResourceMetadata{}, Functions: []fwserver.FunctionMetadata{}, diff --git a/internal/fwserver/server_getproviderschema.go b/internal/fwserver/server_getproviderschema.go index c695a7530..74a25446e 100644 --- a/internal/fwserver/server_getproviderschema.go +++ b/internal/fwserver/server_getproviderschema.go @@ -6,6 +6,7 @@ package fwserver import ( "context" + actionschema "github.com/hashicorp/terraform-plugin-framework/action/schema" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/function" "github.com/hashicorp/terraform-plugin-framework/internal/fwschema" @@ -21,6 +22,7 @@ type GetProviderSchemaResponse struct { ServerCapabilities *ServerCapabilities Provider fwschema.Schema ProviderMeta fwschema.Schema + ActionSchemas map[string]actionschema.SchemaType ResourceSchemas map[string]fwschema.Schema DataSourceSchemas map[string]fwschema.Schema EphemeralResourceSchemas map[string]fwschema.Schema @@ -84,4 +86,11 @@ func (s *Server) GetProviderSchema(ctx context.Context, req *GetProviderSchemaRe return } resp.ListResourceSchemas = listResourceSchemas + + actionSchemas, diags := s.ActionSchemas(ctx) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + resp.ActionSchemas = actionSchemas } diff --git a/internal/fwserver/server_getproviderschema_test.go b/internal/fwserver/server_getproviderschema_test.go index 6caecd976..014ce72f3 100644 --- a/internal/fwserver/server_getproviderschema_test.go +++ b/internal/fwserver/server_getproviderschema_test.go @@ -9,6 +9,8 @@ import ( "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/action" + actionschema "github.com/hashicorp/terraform-plugin-framework/action/schema" "github.com/hashicorp/terraform-plugin-framework/datasource" datasourceschema "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/diag" @@ -40,6 +42,305 @@ func TestServerGetProviderSchema(t *testing.T) { Provider: &testprovider.Provider{}, }, expectedResponse: &fwserver.GetProviderSchemaResponse{ + ActionSchemas: map[string]actionschema.SchemaType{}, + DataSourceSchemas: map[string]fwschema.Schema{}, + EphemeralResourceSchemas: map[string]fwschema.Schema{}, + FunctionDefinitions: map[string]function.Definition{}, + ListResourceSchemas: map[string]fwschema.Schema{}, + Provider: providerschema.Schema{}, + ResourceSchemas: map[string]fwschema.Schema{}, + ServerCapabilities: &fwserver.ServerCapabilities{ + GetProviderSchemaOptional: true, + MoveResourceState: true, + PlanDestroy: true, + }, + }, + }, + "actionschemas": { + server: &fwserver.Server{ + Provider: &testprovider.Provider{ + ActionsMethod: func(_ context.Context) []func() action.Action { + return []func() action.Action{ + func() action.Action { + return &testprovider.Action{ + SchemaMethod: func(_ context.Context, _ action.SchemaRequest, resp *action.SchemaResponse) { + resp.Schema = actionschema.UnlinkedSchema{ + Attributes: map[string]actionschema.Attribute{ + "test1": actionschema.StringAttribute{ + Required: true, + }, + }, + } + }, + MetadataMethod: func(_ context.Context, _ action.MetadataRequest, resp *action.MetadataResponse) { + resp.TypeName = "test_action1" + }, + } + }, + func() action.Action { + return &testprovider.Action{ + SchemaMethod: func(_ context.Context, _ action.SchemaRequest, resp *action.SchemaResponse) { + resp.Schema = actionschema.UnlinkedSchema{ + Attributes: map[string]actionschema.Attribute{ + "test2": actionschema.StringAttribute{ + Required: true, + }, + }, + } + }, + MetadataMethod: func(_ context.Context, _ action.MetadataRequest, resp *action.MetadataResponse) { + resp.TypeName = "test_action2" + }, + } + }, + } + }, + }, + }, + request: &fwserver.GetProviderSchemaRequest{}, + expectedResponse: &fwserver.GetProviderSchemaResponse{ + ActionSchemas: map[string]actionschema.SchemaType{ + "test_action1": actionschema.UnlinkedSchema{ + Attributes: map[string]actionschema.Attribute{ + "test1": actionschema.StringAttribute{ + Required: true, + }, + }, + }, + "test_action2": actionschema.UnlinkedSchema{ + Attributes: map[string]actionschema.Attribute{ + "test2": actionschema.StringAttribute{ + Required: true, + }, + }, + }, + }, + EphemeralResourceSchemas: map[string]fwschema.Schema{}, + FunctionDefinitions: map[string]function.Definition{}, + ListResourceSchemas: map[string]fwschema.Schema{}, + Provider: providerschema.Schema{}, + ResourceSchemas: map[string]fwschema.Schema{}, + ServerCapabilities: &fwserver.ServerCapabilities{ + GetProviderSchemaOptional: true, + MoveResourceState: true, + PlanDestroy: true, + }, + ProviderMeta: nil, + DataSourceSchemas: map[string]fwschema.Schema{}, + Diagnostics: diag.Diagnostics{}, + }, + }, + "actionschemas-invalid-attribute-name": { + server: &fwserver.Server{ + Provider: &testprovider.Provider{ + ActionsMethod: func(_ context.Context) []func() action.Action { + return []func() action.Action{ + func() action.Action { + return &testprovider.Action{ + SchemaMethod: func(_ context.Context, _ action.SchemaRequest, resp *action.SchemaResponse) { + resp.Schema = actionschema.UnlinkedSchema{ + Attributes: map[string]actionschema.Attribute{ + "$": actionschema.StringAttribute{ + Required: true, + }, + }, + } + }, + MetadataMethod: func(_ context.Context, _ action.MetadataRequest, resp *action.MetadataResponse) { + resp.TypeName = "test_action1" + }, + } + }, + func() action.Action { + return &testprovider.Action{ + SchemaMethod: func(_ context.Context, _ action.SchemaRequest, resp *action.SchemaResponse) { + resp.Schema = actionschema.UnlinkedSchema{ + Attributes: map[string]actionschema.Attribute{ + "test2": actionschema.StringAttribute{ + Required: true, + }, + }, + } + }, + MetadataMethod: func(_ context.Context, _ action.MetadataRequest, resp *action.MetadataResponse) { + resp.TypeName = "test_action2" + }, + } + }, + } + }, + }, + }, + request: &fwserver.GetProviderSchemaRequest{}, + expectedResponse: &fwserver.GetProviderSchemaResponse{ + Provider: providerschema.Schema{}, + ServerCapabilities: &fwserver.ServerCapabilities{ + GetProviderSchemaOptional: true, + MoveResourceState: true, + PlanDestroy: true, + }, + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Invalid Attribute/Block Name", + "When validating the schema, an implementation issue was found. "+ + "This is always an issue with the provider and should be reported to the provider developers.\n\n"+ + "\"$\" at schema path \"$\" is an invalid attribute/block name. "+ + "Names must only contain lowercase alphanumeric characters (a-z, 0-9) and underscores (_).", + ), + }, + ProviderMeta: nil, + ResourceSchemas: map[string]fwschema.Schema{}, + DataSourceSchemas: map[string]fwschema.Schema{}, + EphemeralResourceSchemas: map[string]fwschema.Schema{}, + FunctionDefinitions: map[string]function.Definition{}, + ListResourceSchemas: map[string]fwschema.Schema{}, + }, + }, + "actionschemas-duplicate-type-name": { + server: &fwserver.Server{ + Provider: &testprovider.Provider{ + ActionsMethod: func(_ context.Context) []func() action.Action { + return []func() action.Action{ + func() action.Action { + return &testprovider.Action{ + SchemaMethod: func(_ context.Context, _ action.SchemaRequest, resp *action.SchemaResponse) { + resp.Schema = actionschema.UnlinkedSchema{ + Attributes: map[string]actionschema.Attribute{ + "test1": actionschema.StringAttribute{ + Required: true, + }, + }, + } + }, + MetadataMethod: func(_ context.Context, _ action.MetadataRequest, resp *action.MetadataResponse) { + resp.TypeName = "test_action" + }, + } + }, + func() action.Action { + return &testprovider.Action{ + SchemaMethod: func(_ context.Context, _ action.SchemaRequest, resp *action.SchemaResponse) { + resp.Schema = actionschema.UnlinkedSchema{ + Attributes: map[string]actionschema.Attribute{ + "test2": actionschema.StringAttribute{ + Required: true, + }, + }, + } + }, + MetadataMethod: func(_ context.Context, _ action.MetadataRequest, resp *action.MetadataResponse) { + resp.TypeName = "test_action" + }, + } + }, + } + }, + }, + }, + request: &fwserver.GetProviderSchemaRequest{}, + expectedResponse: &fwserver.GetProviderSchemaResponse{ + ActionSchemas: nil, + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Duplicate Action Defined", + "The test_action action type was returned for multiple actions. "+ + "Action types must be unique. "+ + "This is always an issue with the provider and should be reported to the provider developers.", + ), + }, + Provider: providerschema.Schema{}, + ResourceSchemas: map[string]fwschema.Schema{}, + ServerCapabilities: &fwserver.ServerCapabilities{ + GetProviderSchemaOptional: true, + MoveResourceState: true, + PlanDestroy: true, + }, + ProviderMeta: nil, + DataSourceSchemas: map[string]fwschema.Schema{}, + EphemeralResourceSchemas: map[string]fwschema.Schema{}, + FunctionDefinitions: map[string]function.Definition{}, + ListResourceSchemas: map[string]fwschema.Schema{}, + }, + }, + "actionschemas-empty-type-name": { + server: &fwserver.Server{ + Provider: &testprovider.Provider{ + ActionsMethod: func(_ context.Context) []func() action.Action { + return []func() action.Action{ + func() action.Action { + return &testprovider.Action{ + MetadataMethod: func(_ context.Context, _ action.MetadataRequest, resp *action.MetadataResponse) { + resp.TypeName = "" + }, + } + }, + } + }, + }, + }, + request: &fwserver.GetProviderSchemaRequest{}, + expectedResponse: &fwserver.GetProviderSchemaResponse{ + ActionSchemas: nil, + Diagnostics: diag.Diagnostics{ + diag.NewErrorDiagnostic( + "Action Type Missing", + "The *testprovider.Action Action returned an empty string from the Metadata method. "+ + "This is always an issue with the provider and should be reported to the provider developers.", + ), + }, + Provider: providerschema.Schema{}, + ResourceSchemas: map[string]fwschema.Schema{}, + ServerCapabilities: &fwserver.ServerCapabilities{ + GetProviderSchemaOptional: true, + MoveResourceState: true, + PlanDestroy: true, + }, + ProviderMeta: nil, + DataSourceSchemas: map[string]fwschema.Schema{}, + EphemeralResourceSchemas: map[string]fwschema.Schema{}, + FunctionDefinitions: map[string]function.Definition{}, + ListResourceSchemas: map[string]fwschema.Schema{}, + }, + }, + "actionschemas-provider-type-name": { + server: &fwserver.Server{ + Provider: &testprovider.Provider{ + MetadataMethod: func(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) { + resp.TypeName = "testprovidertype" + }, + ActionsMethod: func(_ context.Context) []func() action.Action { + return []func() action.Action{ + func() action.Action { + return &testprovider.Action{ + SchemaMethod: func(_ context.Context, _ action.SchemaRequest, resp *action.SchemaResponse) { + resp.Schema = actionschema.UnlinkedSchema{ + Attributes: map[string]actionschema.Attribute{ + "test": actionschema.StringAttribute{ + Required: true, + }, + }, + } + }, + MetadataMethod: func(_ context.Context, req action.MetadataRequest, resp *action.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_action" + }, + } + }, + } + }, + }, + }, + request: &fwserver.GetProviderSchemaRequest{}, + expectedResponse: &fwserver.GetProviderSchemaResponse{ + ActionSchemas: map[string]actionschema.SchemaType{ + "testprovidertype_action": actionschema.UnlinkedSchema{ + Attributes: map[string]actionschema.Attribute{ + "test": actionschema.StringAttribute{ + Required: true, + }, + }, + }, + }, DataSourceSchemas: map[string]fwschema.Schema{}, EphemeralResourceSchemas: map[string]fwschema.Schema{}, FunctionDefinitions: map[string]function.Definition{}, @@ -96,6 +397,7 @@ func TestServerGetProviderSchema(t *testing.T) { }, request: &fwserver.GetProviderSchemaRequest{}, expectedResponse: &fwserver.GetProviderSchemaResponse{ + ActionSchemas: map[string]actionschema.SchemaType{}, DataSourceSchemas: map[string]fwschema.Schema{ "test_data_source1": datasourceschema.Schema{ Attributes: map[string]datasourceschema.Attribute{ @@ -311,6 +613,7 @@ func TestServerGetProviderSchema(t *testing.T) { }, request: &fwserver.GetProviderSchemaRequest{}, expectedResponse: &fwserver.GetProviderSchemaResponse{ + ActionSchemas: map[string]actionschema.SchemaType{}, DataSourceSchemas: map[string]fwschema.Schema{ "testprovidertype_data_source": datasourceschema.Schema{ Attributes: map[string]datasourceschema.Attribute{ @@ -375,6 +678,7 @@ func TestServerGetProviderSchema(t *testing.T) { }, request: &fwserver.GetProviderSchemaRequest{}, expectedResponse: &fwserver.GetProviderSchemaResponse{ + ActionSchemas: map[string]actionschema.SchemaType{}, DataSourceSchemas: map[string]fwschema.Schema{}, EphemeralResourceSchemas: map[string]fwschema.Schema{ "test_ephemeral_resource1": ephemeralschema.Schema{ @@ -596,6 +900,7 @@ func TestServerGetProviderSchema(t *testing.T) { }, request: &fwserver.GetProviderSchemaRequest{}, expectedResponse: &fwserver.GetProviderSchemaResponse{ + ActionSchemas: map[string]actionschema.SchemaType{}, DataSourceSchemas: map[string]fwschema.Schema{}, EphemeralResourceSchemas: map[string]fwschema.Schema{ "testprovidertype_ephemeral_resource": ephemeralschema.Schema{ @@ -652,6 +957,7 @@ func TestServerGetProviderSchema(t *testing.T) { }, request: &fwserver.GetProviderSchemaRequest{}, expectedResponse: &fwserver.GetProviderSchemaResponse{ + ActionSchemas: map[string]actionschema.SchemaType{}, DataSourceSchemas: map[string]fwschema.Schema{}, EphemeralResourceSchemas: map[string]fwschema.Schema{}, FunctionDefinitions: map[string]function.Definition{ @@ -863,6 +1169,7 @@ func TestServerGetProviderSchema(t *testing.T) { }, request: &fwserver.GetProviderSchemaRequest{}, expectedResponse: &fwserver.GetProviderSchemaResponse{ + ActionSchemas: map[string]actionschema.SchemaType{}, DataSourceSchemas: map[string]fwschema.Schema{}, EphemeralResourceSchemas: map[string]fwschema.Schema{}, FunctionDefinitions: map[string]function.Definition{}, @@ -984,6 +1291,7 @@ func TestServerGetProviderSchema(t *testing.T) { }, request: &fwserver.GetProviderSchemaRequest{}, expectedResponse: &fwserver.GetProviderSchemaResponse{ + ActionSchemas: map[string]actionschema.SchemaType{}, DataSourceSchemas: map[string]fwschema.Schema{}, EphemeralResourceSchemas: map[string]fwschema.Schema{}, FunctionDefinitions: map[string]function.Definition{}, @@ -1052,6 +1360,7 @@ func TestServerGetProviderSchema(t *testing.T) { }, request: &fwserver.GetProviderSchemaRequest{}, expectedResponse: &fwserver.GetProviderSchemaResponse{ + ActionSchemas: map[string]actionschema.SchemaType{}, DataSourceSchemas: map[string]fwschema.Schema{}, EphemeralResourceSchemas: map[string]fwschema.Schema{}, FunctionDefinitions: map[string]function.Definition{}, @@ -1149,6 +1458,7 @@ func TestServerGetProviderSchema(t *testing.T) { }, request: &fwserver.GetProviderSchemaRequest{}, expectedResponse: &fwserver.GetProviderSchemaResponse{ + ActionSchemas: map[string]actionschema.SchemaType{}, DataSourceSchemas: map[string]fwschema.Schema{}, EphemeralResourceSchemas: map[string]fwschema.Schema{}, FunctionDefinitions: map[string]function.Definition{}, @@ -1363,6 +1673,7 @@ func TestServerGetProviderSchema(t *testing.T) { }, request: &fwserver.GetProviderSchemaRequest{}, expectedResponse: &fwserver.GetProviderSchemaResponse{ + ActionSchemas: map[string]actionschema.SchemaType{}, DataSourceSchemas: map[string]fwschema.Schema{}, EphemeralResourceSchemas: map[string]fwschema.Schema{}, FunctionDefinitions: map[string]function.Definition{}, diff --git a/internal/testing/testprovider/action.go b/internal/testing/testprovider/action.go new file mode 100644 index 000000000..610ee8326 --- /dev/null +++ b/internal/testing/testprovider/action.go @@ -0,0 +1,37 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package testprovider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/action" +) + +var _ action.Action = &Action{} + +// Declarative action.Action for unit testing. +type Action struct { + // Action interface methods + MetadataMethod func(context.Context, action.MetadataRequest, *action.MetadataResponse) + SchemaMethod func(context.Context, action.SchemaRequest, *action.SchemaResponse) +} + +// Metadata satisfies the action.Action interface. +func (d *Action) Metadata(ctx context.Context, req action.MetadataRequest, resp *action.MetadataResponse) { + if d.MetadataMethod == nil { + return + } + + d.MetadataMethod(ctx, req, resp) +} + +// Schema satisfies the action.Action interface. +func (d *Action) Schema(ctx context.Context, req action.SchemaRequest, resp *action.SchemaResponse) { + if d.SchemaMethod == nil { + return + } + + d.SchemaMethod(ctx, req, resp) +} diff --git a/internal/testing/testprovider/provider.go b/internal/testing/testprovider/provider.go index efad3b8a4..b039be76d 100644 --- a/internal/testing/testprovider/provider.go +++ b/internal/testing/testprovider/provider.go @@ -6,6 +6,7 @@ package testprovider import ( "context" + "github.com/hashicorp/terraform-plugin-framework/action" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/ephemeral" "github.com/hashicorp/terraform-plugin-framework/list" @@ -21,6 +22,7 @@ type Provider struct { MetadataMethod func(context.Context, provider.MetadataRequest, *provider.MetadataResponse) ConfigureMethod func(context.Context, provider.ConfigureRequest, *provider.ConfigureResponse) SchemaMethod func(context.Context, provider.SchemaRequest, *provider.SchemaResponse) + ActionsMethod func(context.Context) []func() action.Action DataSourcesMethod func(context.Context) []func() datasource.DataSource EphemeralResourcesMethod func(context.Context) []func() ephemeral.EphemeralResource ListResourcesMethod func(context.Context) []func() list.ListResource @@ -36,6 +38,15 @@ func (p *Provider) Configure(ctx context.Context, req provider.ConfigureRequest, p.ConfigureMethod(ctx, req, resp) } +// Actions satisfies the provider.Provider interface. +func (p *Provider) Actions(ctx context.Context) []func() action.Action { + if p == nil || p.ActionsMethod == nil { + return nil + } + + return p.ActionsMethod(ctx) +} + // DataSources satisfies the provider.Provider interface. func (p *Provider) DataSources(ctx context.Context) []func() datasource.DataSource { if p == nil || p.DataSourcesMethod == nil { diff --git a/internal/toproto5/action_schema.go b/internal/toproto5/action_schema.go new file mode 100644 index 000000000..3b3296668 --- /dev/null +++ b/internal/toproto5/action_schema.go @@ -0,0 +1,42 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package toproto5 + +import ( + "context" + "fmt" + + actionschema "github.com/hashicorp/terraform-plugin-framework/action/schema" + "github.com/hashicorp/terraform-plugin-go/tfprotov5" +) + +// ActionSchema returns the *tfprotov5.ActionSchema equivalent of a ActionSchema. +func ActionSchema(ctx context.Context, s actionschema.SchemaType) (*tfprotov5.ActionSchema, error) { + if s == nil { + return nil, nil + } + + configSchema, err := Schema(ctx, s) + if err != nil { + return nil, err + } + + result := &tfprotov5.ActionSchema{ + Schema: configSchema, + } + + // TODO:Actions: Implement linked and lifecycle action schema types + switch s.(type) { + case actionschema.UnlinkedSchema: + result.Type = tfprotov5.UnlinkedActionSchemaType{} + default: + // It is not currently possible to create [actionschema.SchemaType] + // implementations outside the "action/schema" package. If this error was reached, + // it implies that a new event type was introduced and needs to be implemented + // as a new case above. + return nil, fmt.Errorf("unimplemented schema.SchemaType type: %T", s) + } + + return result, nil +} diff --git a/internal/toproto5/action_schema_test.go b/internal/toproto5/action_schema_test.go new file mode 100644 index 000000000..717d19162 --- /dev/null +++ b/internal/toproto5/action_schema_test.go @@ -0,0 +1,125 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package toproto5_test + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/action/schema" + actionschema "github.com/hashicorp/terraform-plugin-framework/action/schema" + "github.com/hashicorp/terraform-plugin-framework/internal/toproto5" + "github.com/hashicorp/terraform-plugin-go/tfprotov5" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +func TestActionSchema(t *testing.T) { + t.Parallel() + + type testCase struct { + input actionschema.SchemaType + expected *tfprotov5.ActionSchema + expectedErr string + } + + tests := map[string]testCase{ + "nil": { + input: nil, + expected: nil, + }, + "unlinked": { + input: actionschema.UnlinkedSchema{ + Attributes: map[string]actionschema.Attribute{ + "bool": actionschema.BoolAttribute{ + Optional: true, + }, + "string": actionschema.StringAttribute{ + Required: true, + }, + }, + Blocks: map[string]actionschema.Block{ + "single_block": schema.SingleNestedBlock{ + Attributes: map[string]actionschema.Attribute{ + "bool": actionschema.BoolAttribute{ + Required: true, + }, + "string": actionschema.StringAttribute{ + Optional: true, + }, + }, + }, + }, + }, + expected: &tfprotov5.ActionSchema{ + Type: tfprotov5.UnlinkedActionSchemaType{}, + Schema: &tfprotov5.Schema{ + Version: 0, + Block: &tfprotov5.SchemaBlock{ + Attributes: []*tfprotov5.SchemaAttribute{ + { + Name: "bool", + Type: tftypes.Bool, + Optional: true, + }, + { + Name: "string", + Type: tftypes.String, + Required: true, + }, + }, + BlockTypes: []*tfprotov5.SchemaNestedBlock{ + { + TypeName: "single_block", + Block: &tfprotov5.SchemaBlock{ + Attributes: []*tfprotov5.SchemaAttribute{ + { + Name: "bool", + Type: tftypes.Bool, + Required: true, + }, + { + Name: "string", + Type: tftypes.String, + Optional: true, + }, + }, + }, + Nesting: tfprotov5.SchemaNestedBlockNestingModeSingle, + }, + }, + }, + }, + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got, err := toproto5.ActionSchema(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 + } + }) + } +} diff --git a/internal/toproto5/actionmetadata.go b/internal/toproto5/actionmetadata.go new file mode 100644 index 000000000..7f7fb5a7b --- /dev/null +++ b/internal/toproto5/actionmetadata.go @@ -0,0 +1,19 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package toproto5 + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/internal/fwserver" + "github.com/hashicorp/terraform-plugin-go/tfprotov5" +) + +// ActionMetadata returns the tfprotov5.ActionMetadata for a +// fwserver.ActionMetadata. +func ActionMetadata(ctx context.Context, fw fwserver.ActionMetadata) tfprotov5.ActionMetadata { + return tfprotov5.ActionMetadata{ + TypeName: fw.TypeName, + } +} diff --git a/internal/toproto5/actionmetadata_test.go b/internal/toproto5/actionmetadata_test.go new file mode 100644 index 000000000..d3707f46f --- /dev/null +++ b/internal/toproto5/actionmetadata_test.go @@ -0,0 +1,44 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package toproto5_test + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/internal/fwserver" + "github.com/hashicorp/terraform-plugin-framework/internal/toproto5" + "github.com/hashicorp/terraform-plugin-go/tfprotov5" +) + +func TestActionMetadata(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + fw fwserver.ActionMetadata + expected tfprotov5.ActionMetadata + }{ + "TypeName": { + fw: fwserver.ActionMetadata{ + TypeName: "test", + }, + expected: tfprotov5.ActionMetadata{ + TypeName: "test", + }, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := toproto5.ActionMetadata(context.Background(), testCase.fw) + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} diff --git a/internal/toproto5/getmetadata.go b/internal/toproto5/getmetadata.go index 8039a892c..608aab93e 100644 --- a/internal/toproto5/getmetadata.go +++ b/internal/toproto5/getmetadata.go @@ -18,6 +18,7 @@ func GetMetadataResponse(ctx context.Context, fw *fwserver.GetMetadataResponse) } protov5 := &tfprotov5.GetMetadataResponse{ + Actions: make([]tfprotov5.ActionMetadata, 0, len(fw.Actions)), DataSources: make([]tfprotov5.DataSourceMetadata, 0, len(fw.DataSources)), Diagnostics: Diagnostics(ctx, fw.Diagnostics), EphemeralResources: make([]tfprotov5.EphemeralResourceMetadata, 0, len(fw.EphemeralResources)), @@ -27,6 +28,10 @@ func GetMetadataResponse(ctx context.Context, fw *fwserver.GetMetadataResponse) ServerCapabilities: ServerCapabilities(ctx, fw.ServerCapabilities), } + for _, action := range fw.Actions { + protov5.Actions = append(protov5.Actions, ActionMetadata(ctx, action)) + } + for _, datasource := range fw.DataSources { protov5.DataSources = append(protov5.DataSources, DataSourceMetadata(ctx, datasource)) } diff --git a/internal/toproto5/getmetadata_test.go b/internal/toproto5/getmetadata_test.go index 2180ec781..e0c4c1960 100644 --- a/internal/toproto5/getmetadata_test.go +++ b/internal/toproto5/getmetadata_test.go @@ -25,6 +25,33 @@ func TestGetMetadataResponse(t *testing.T) { input: nil, expected: nil, }, + "actions": { + input: &fwserver.GetMetadataResponse{ + Actions: []fwserver.ActionMetadata{ + { + TypeName: "test_action_1", + }, + { + TypeName: "test_action_2", + }, + }, + }, + expected: &tfprotov5.GetMetadataResponse{ + Actions: []tfprotov5.ActionMetadata{ + { + TypeName: "test_action_1", + }, + { + TypeName: "test_action_2", + }, + }, + DataSources: []tfprotov5.DataSourceMetadata{}, + EphemeralResources: []tfprotov5.EphemeralResourceMetadata{}, + Functions: []tfprotov5.FunctionMetadata{}, + ListResources: []tfprotov5.ListResourceMetadata{}, + Resources: []tfprotov5.ResourceMetadata{}, + }, + }, "datasources": { input: &fwserver.GetMetadataResponse{ DataSources: []fwserver.DataSourceMetadata{ @@ -37,6 +64,7 @@ func TestGetMetadataResponse(t *testing.T) { }, }, expected: &tfprotov5.GetMetadataResponse{ + Actions: []tfprotov5.ActionMetadata{}, DataSources: []tfprotov5.DataSourceMetadata{ { TypeName: "test_data_source_1", @@ -63,6 +91,7 @@ func TestGetMetadataResponse(t *testing.T) { }, }, expected: &tfprotov5.GetMetadataResponse{ + Actions: []tfprotov5.ActionMetadata{}, DataSources: []tfprotov5.DataSourceMetadata{}, EphemeralResources: []tfprotov5.EphemeralResourceMetadata{ { @@ -89,6 +118,7 @@ func TestGetMetadataResponse(t *testing.T) { }, }, expected: &tfprotov5.GetMetadataResponse{ + Actions: []tfprotov5.ActionMetadata{}, DataSources: []tfprotov5.DataSourceMetadata{}, Diagnostics: []*tfprotov5.Diagnostic{ { @@ -117,6 +147,7 @@ func TestGetMetadataResponse(t *testing.T) { }, }, expected: &tfprotov5.GetMetadataResponse{ + Actions: []tfprotov5.ActionMetadata{}, DataSources: []tfprotov5.DataSourceMetadata{}, EphemeralResources: []tfprotov5.EphemeralResourceMetadata{}, Functions: []tfprotov5.FunctionMetadata{ @@ -143,6 +174,7 @@ func TestGetMetadataResponse(t *testing.T) { }, }, expected: &tfprotov5.GetMetadataResponse{ + Actions: []tfprotov5.ActionMetadata{}, DataSources: []tfprotov5.DataSourceMetadata{}, EphemeralResources: []tfprotov5.EphemeralResourceMetadata{}, Functions: []tfprotov5.FunctionMetadata{}, @@ -169,6 +201,7 @@ func TestGetMetadataResponse(t *testing.T) { }, }, expected: &tfprotov5.GetMetadataResponse{ + Actions: []tfprotov5.ActionMetadata{}, DataSources: []tfprotov5.DataSourceMetadata{}, EphemeralResources: []tfprotov5.EphemeralResourceMetadata{}, Functions: []tfprotov5.FunctionMetadata{}, @@ -191,6 +224,7 @@ func TestGetMetadataResponse(t *testing.T) { }, }, expected: &tfprotov5.GetMetadataResponse{ + Actions: []tfprotov5.ActionMetadata{}, DataSources: []tfprotov5.DataSourceMetadata{}, EphemeralResources: []tfprotov5.EphemeralResourceMetadata{}, Functions: []tfprotov5.FunctionMetadata{}, diff --git a/internal/toproto5/getproviderschema.go b/internal/toproto5/getproviderschema.go index be12254dc..98e6bee24 100644 --- a/internal/toproto5/getproviderschema.go +++ b/internal/toproto5/getproviderschema.go @@ -18,6 +18,7 @@ func GetProviderSchemaResponse(ctx context.Context, fw *fwserver.GetProviderSche } protov5 := &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: make(map[string]*tfprotov5.ActionSchema, len(fw.ActionSchemas)), DataSourceSchemas: make(map[string]*tfprotov5.Schema, len(fw.DataSourceSchemas)), Diagnostics: Diagnostics(ctx, fw.Diagnostics), EphemeralResourceSchemas: make(map[string]*tfprotov5.Schema, len(fw.EphemeralResourceSchemas)), @@ -53,6 +54,20 @@ func GetProviderSchemaResponse(ctx context.Context, fw *fwserver.GetProviderSche return protov5 } + for actionType, actionSchema := range fw.ActionSchemas { + protov5.ActionSchemas[actionType], err = ActionSchema(ctx, actionSchema) + + if err != nil { + protov5.Diagnostics = append(protov5.Diagnostics, &tfprotov5.Diagnostic{ + Severity: tfprotov5.DiagnosticSeverityError, + Summary: "Error converting action schema", + Detail: "The schema for the action \"" + actionType + "\" couldn't be converted into a usable type. This is always a problem with the provider. Please report the following to the provider developer:\n\n" + err.Error(), + }) + + return protov5 + } + } + for dataSourceType, dataSourceSchema := range fw.DataSourceSchemas { protov5.DataSourceSchemas[dataSourceType], err = Schema(ctx, dataSourceSchema) diff --git a/internal/toproto5/getproviderschema_test.go b/internal/toproto5/getproviderschema_test.go index f23925017..807850a07 100644 --- a/internal/toproto5/getproviderschema_test.go +++ b/internal/toproto5/getproviderschema_test.go @@ -11,6 +11,7 @@ import ( "github.com/hashicorp/terraform-plugin-go/tfprotov5" "github.com/hashicorp/terraform-plugin-go/tftypes" + actionschema "github.com/hashicorp/terraform-plugin-framework/action/schema" "github.com/hashicorp/terraform-plugin-framework/attr" datasourceschema "github.com/hashicorp/terraform-plugin-framework/datasource/schema" ephemeralschema "github.com/hashicorp/terraform-plugin-framework/ephemeral/schema" @@ -38,6 +39,102 @@ func TestGetProviderSchemaResponse(t *testing.T) { input: nil, expected: nil, }, + "action-multiple-actions": { + input: &fwserver.GetProviderSchemaResponse{ + ActionSchemas: map[string]actionschema.SchemaType{ + "test_action_1": actionschema.UnlinkedSchema{ + Attributes: map[string]actionschema.Attribute{ + "test_attribute": actionschema.StringAttribute{ + Required: true, + }, + }, + }, + "test_action_2": actionschema.UnlinkedSchema{ + Attributes: map[string]actionschema.Attribute{ + "test_attribute": actionschema.StringAttribute{ + Optional: true, + DeprecationMessage: "deprecated", + }, + }, + }, + }, + }, + expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{ + "test_action_1": { + Type: tfprotov5.UnlinkedActionSchemaType{}, + Schema: &tfprotov5.Schema{ + Version: 0, + Block: &tfprotov5.SchemaBlock{ + Attributes: []*tfprotov5.SchemaAttribute{ + { + Name: "test_attribute", + Type: tftypes.String, + Required: true, + }, + }, + }, + }, + }, + "test_action_2": { + Type: tfprotov5.UnlinkedActionSchemaType{}, + Schema: &tfprotov5.Schema{ + Version: 0, + Block: &tfprotov5.SchemaBlock{ + Attributes: []*tfprotov5.SchemaAttribute{ + { + Name: "test_attribute", + Type: tftypes.String, + Optional: true, + Deprecated: true, + }, + }, + }, + }, + }, + }, + DataSourceSchemas: map[string]*tfprotov5.Schema{}, + EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, + Functions: map[string]*tfprotov5.Function{}, + ListResourceSchemas: map[string]*tfprotov5.Schema{}, + ResourceSchemas: map[string]*tfprotov5.Schema{}, + }, + }, + "action-type-invalid-proto-6-nested-attributes": { + input: &fwserver.GetProviderSchemaResponse{ + ActionSchemas: map[string]actionschema.SchemaType{ + "test_action": actionschema.UnlinkedSchema{ + Attributes: map[string]actionschema.Attribute{ + "test_attribute": actionschema.SingleNestedAttribute{ + Attributes: map[string]actionschema.Attribute{ + "test_nested_attribute": actionschema.StringAttribute{ + Required: true, + }, + }, + Required: true, + }, + }, + }, + }, + }, + expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{ + "test_action": nil, + }, + DataSourceSchemas: map[string]*tfprotov5.Schema{}, + EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, + Diagnostics: []*tfprotov5.Diagnostic{ + { + Severity: tfprotov5.DiagnosticSeverityError, + Summary: "Error converting action schema", + Detail: "The schema for the action \"test_action\" couldn't be converted into a usable type. This is always a problem with the provider. Please report the following to the provider developer:\n\nAttributeName(\"test_attribute\"): protocol version 5 cannot have Attributes set", + }, + }, + Functions: map[string]*tfprotov5.Function{}, + ListResourceSchemas: map[string]*tfprotov5.Schema{}, + ResourceSchemas: map[string]*tfprotov5.Schema{}, + }, + }, "data-source-multiple-data-sources": { input: &fwserver.GetProviderSchemaResponse{ DataSourceSchemas: map[string]fwschema.Schema{ @@ -58,6 +155,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{ "test_data_source_1": { Block: &tfprotov5.SchemaBlock{ @@ -101,6 +199,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{ "test_data_source": { Block: &tfprotov5.SchemaBlock{ @@ -134,6 +233,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{ "test_data_source": { Block: &tfprotov5.SchemaBlock{ @@ -167,6 +267,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{ "test_data_source": { Block: &tfprotov5.SchemaBlock{ @@ -200,6 +301,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{ "test_data_source": { Block: &tfprotov5.SchemaBlock{ @@ -233,6 +335,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{ "test_data_source": { Block: &tfprotov5.SchemaBlock{ @@ -266,6 +369,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{ "test_data_source": { Block: &tfprotov5.SchemaBlock{ @@ -299,6 +403,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{ "test_data_source": { Block: &tfprotov5.SchemaBlock{ @@ -332,6 +437,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{ "test_data_source": { Block: &tfprotov5.SchemaBlock{ @@ -364,6 +470,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{ "test_data_source": { Block: &tfprotov5.SchemaBlock{ @@ -396,6 +503,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{ "test_data_source": { Block: &tfprotov5.SchemaBlock{ @@ -428,6 +536,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{ "test_data_source": { Block: &tfprotov5.SchemaBlock{ @@ -460,6 +569,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{ "test_data_source": { Block: &tfprotov5.SchemaBlock{ @@ -495,6 +605,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{ "test_data_source": { Block: &tfprotov5.SchemaBlock{ @@ -538,6 +649,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{ "test_data_source": nil, }, @@ -572,6 +684,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{ "test_data_source": { Block: &tfprotov5.SchemaBlock{ @@ -611,6 +724,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{ "test_data_source": { Block: &tfprotov5.SchemaBlock{ @@ -652,6 +766,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{ "test_data_source": nil, }, @@ -682,6 +797,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{ "test_data_source": { Block: &tfprotov5.SchemaBlock{ @@ -716,6 +832,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{ "test_data_source": { Block: &tfprotov5.SchemaBlock{ @@ -751,6 +868,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{ "test_data_source": { Block: &tfprotov5.SchemaBlock{ @@ -794,6 +912,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{ "test_data_source": nil, }, @@ -828,6 +947,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{ "test_data_source": { Block: &tfprotov5.SchemaBlock{ @@ -869,6 +989,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{ "test_data_source": { Block: &tfprotov5.SchemaBlock{ @@ -906,6 +1027,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{ "test_data_source": { Block: &tfprotov5.SchemaBlock{ @@ -945,6 +1067,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{ "test_data_source": nil, }, @@ -974,6 +1097,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{ "test_data_source": { Block: &tfprotov5.SchemaBlock{ @@ -1006,6 +1130,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{ "test_data_source": { Block: &tfprotov5.SchemaBlock{ @@ -1044,6 +1169,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{ "test_data_source": { Block: &tfprotov5.SchemaBlock{ @@ -1090,6 +1216,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{ "test_data_source": { Block: &tfprotov5.SchemaBlock{ @@ -1134,6 +1261,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{ "test_data_source": { Block: &tfprotov5.SchemaBlock{ @@ -1181,6 +1309,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{ "test_ephemeral_resource_1": { @@ -1224,6 +1353,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{ "test_ephemeral_resource": { @@ -1257,6 +1387,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{ "test_ephemeral_resource": { @@ -1290,6 +1421,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{ "test_ephemeral_resource": { @@ -1323,6 +1455,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{ "test_ephemeral_resource": { @@ -1356,6 +1489,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{ "test_ephemeral_resource": { @@ -1389,6 +1523,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{ "test_ephemeral_resource": { @@ -1422,6 +1557,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{ "test_ephemeral_resource": { @@ -1455,6 +1591,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{ "test_ephemeral_resource": { @@ -1487,6 +1624,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{ "test_ephemeral_resource": { @@ -1519,6 +1657,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{ "test_ephemeral_resource": { @@ -1551,6 +1690,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{ "test_ephemeral_resource": { @@ -1583,6 +1723,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{ "test_ephemeral_resource": { @@ -1618,6 +1759,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{ "test_ephemeral_resource": { @@ -1661,6 +1803,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{ "test_ephemeral_resource": nil, @@ -1695,6 +1838,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{ "test_ephemeral_resource": { @@ -1734,6 +1878,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{ "test_ephemeral_resource": { @@ -1775,6 +1920,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{ "test_ephemeral_resource": nil, @@ -1805,6 +1951,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{ "test_ephemeral_resource": { @@ -1839,6 +1986,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{ "test_ephemeral_resource": { @@ -1874,6 +2022,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{ "test_ephemeral_resource": { @@ -1917,6 +2066,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{ "test_ephemeral_resource": nil, @@ -1951,6 +2101,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{ "test_ephemeral_resource": { @@ -1992,6 +2143,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{ "test_ephemeral_resource": { @@ -2029,6 +2181,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{ "test_ephemeral_resource": { @@ -2068,6 +2221,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{ "test_ephemeral_resource": nil, @@ -2097,6 +2251,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{ "test_ephemeral_resource": { @@ -2129,6 +2284,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{ "test_ephemeral_resource": { @@ -2167,6 +2323,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{ "test_ephemeral_resource": { @@ -2213,6 +2370,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{ "test_ephemeral_resource": { @@ -2257,6 +2415,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{ "test_ephemeral_resource": { @@ -2296,6 +2455,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{ @@ -2326,6 +2486,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{ @@ -2351,6 +2512,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{ @@ -2380,6 +2542,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{ @@ -2413,6 +2576,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{ @@ -2437,6 +2601,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{ @@ -2462,6 +2627,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{ @@ -2499,6 +2665,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -2543,6 +2710,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -2576,6 +2744,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -2608,6 +2777,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -2640,6 +2810,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -2671,6 +2842,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -2701,6 +2873,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -2730,6 +2903,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -2760,6 +2934,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -2790,6 +2965,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -2820,6 +2996,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -2849,6 +3026,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -2878,6 +3056,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -2907,6 +3086,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -2936,6 +3116,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -2968,6 +3149,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -3008,6 +3190,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Diagnostics: []*tfprotov5.Diagnostic{ @@ -3038,6 +3221,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -3074,6 +3258,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -3112,6 +3297,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Diagnostics: []*tfprotov5.Diagnostic{ @@ -3138,6 +3324,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -3169,6 +3356,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -3201,6 +3389,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -3241,6 +3430,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Diagnostics: []*tfprotov5.Diagnostic{ @@ -3271,6 +3461,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -3309,6 +3500,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -3343,6 +3535,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -3379,6 +3572,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Diagnostics: []*tfprotov5.Diagnostic{ @@ -3404,6 +3598,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -3433,6 +3628,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -3468,6 +3664,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -3511,6 +3708,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -3552,6 +3750,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -3589,6 +3788,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -3618,6 +3818,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -3647,6 +3848,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -3676,6 +3878,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -3705,6 +3908,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -3737,6 +3941,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -3777,6 +3982,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Diagnostics: []*tfprotov5.Diagnostic{ @@ -3807,6 +4013,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, ProviderMeta: &tfprotov5.Schema{ @@ -3843,6 +4050,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -3881,6 +4089,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Diagnostics: []*tfprotov5.Diagnostic{ @@ -3907,6 +4116,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -3938,6 +4148,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -3970,6 +4181,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -4010,6 +4222,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Diagnostics: []*tfprotov5.Diagnostic{ @@ -4040,6 +4253,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -4078,6 +4292,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -4112,6 +4327,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -4148,6 +4364,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Diagnostics: []*tfprotov5.Diagnostic{ @@ -4173,6 +4390,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -4211,6 +4429,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -4254,6 +4473,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -4287,6 +4507,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -4320,6 +4541,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -4353,6 +4575,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -4386,6 +4609,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -4419,6 +4643,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -4453,6 +4678,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -4486,6 +4712,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -4518,6 +4745,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -4550,6 +4778,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -4582,6 +4811,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -4614,6 +4844,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -4649,6 +4880,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -4692,6 +4924,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Diagnostics: []*tfprotov5.Diagnostic{ @@ -4726,6 +4959,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -4765,6 +4999,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -4806,6 +5041,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Diagnostics: []*tfprotov5.Diagnostic{ @@ -4836,6 +5072,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -4870,6 +5107,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -4905,6 +5143,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -4948,6 +5187,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Diagnostics: []*tfprotov5.Diagnostic{ @@ -4982,6 +5222,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -5023,6 +5264,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -5060,6 +5302,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -5099,6 +5342,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Diagnostics: []*tfprotov5.Diagnostic{ @@ -5128,6 +5372,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -5160,6 +5405,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -5198,6 +5444,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -5244,6 +5491,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -5288,6 +5536,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -5324,6 +5573,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, diff --git a/internal/toproto6/action_schema.go b/internal/toproto6/action_schema.go new file mode 100644 index 000000000..94aaa7416 --- /dev/null +++ b/internal/toproto6/action_schema.go @@ -0,0 +1,42 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package toproto6 + +import ( + "context" + "fmt" + + actionschema "github.com/hashicorp/terraform-plugin-framework/action/schema" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" +) + +// ActionSchema returns the *tfprotov6.ActionSchema equivalent of a ActionSchema. +func ActionSchema(ctx context.Context, s actionschema.SchemaType) (*tfprotov6.ActionSchema, error) { + if s == nil { + return nil, nil + } + + configSchema, err := Schema(ctx, s) + if err != nil { + return nil, err + } + + result := &tfprotov6.ActionSchema{ + Schema: configSchema, + } + + // TODO:Actions: Implement linked and lifecycle action schema types + switch s.(type) { + case actionschema.UnlinkedSchema: + result.Type = tfprotov6.UnlinkedActionSchemaType{} + default: + // It is not currently possible to create [actionschema.SchemaType] + // implementations outside the "action/schema" package. If this error was reached, + // it implies that a new event type was introduced and needs to be implemented + // as a new case above. + return nil, fmt.Errorf("unimplemented schema.SchemaType type: %T", s) + } + + return result, nil +} diff --git a/internal/toproto6/action_schema_test.go b/internal/toproto6/action_schema_test.go new file mode 100644 index 000000000..20de2d0f6 --- /dev/null +++ b/internal/toproto6/action_schema_test.go @@ -0,0 +1,125 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package toproto6_test + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/action/schema" + actionschema "github.com/hashicorp/terraform-plugin-framework/action/schema" + "github.com/hashicorp/terraform-plugin-framework/internal/toproto6" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +func TestActionSchema(t *testing.T) { + t.Parallel() + + type testCase struct { + input actionschema.SchemaType + expected *tfprotov6.ActionSchema + expectedErr string + } + + tests := map[string]testCase{ + "nil": { + input: nil, + expected: nil, + }, + "unlinked": { + input: actionschema.UnlinkedSchema{ + Attributes: map[string]actionschema.Attribute{ + "bool": actionschema.BoolAttribute{ + Optional: true, + }, + "string": actionschema.StringAttribute{ + Required: true, + }, + }, + Blocks: map[string]actionschema.Block{ + "single_block": schema.SingleNestedBlock{ + Attributes: map[string]actionschema.Attribute{ + "bool": actionschema.BoolAttribute{ + Required: true, + }, + "string": actionschema.StringAttribute{ + Optional: true, + }, + }, + }, + }, + }, + expected: &tfprotov6.ActionSchema{ + Type: tfprotov6.UnlinkedActionSchemaType{}, + Schema: &tfprotov6.Schema{ + Version: 0, + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "bool", + Type: tftypes.Bool, + Optional: true, + }, + { + Name: "string", + Type: tftypes.String, + Required: true, + }, + }, + BlockTypes: []*tfprotov6.SchemaNestedBlock{ + { + TypeName: "single_block", + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "bool", + Type: tftypes.Bool, + Required: true, + }, + { + Name: "string", + Type: tftypes.String, + Optional: true, + }, + }, + }, + Nesting: tfprotov6.SchemaNestedBlockNestingModeSingle, + }, + }, + }, + }, + }, + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got, err := toproto6.ActionSchema(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 + } + }) + } +} diff --git a/internal/toproto6/actionmetadata.go b/internal/toproto6/actionmetadata.go new file mode 100644 index 000000000..fb527d867 --- /dev/null +++ b/internal/toproto6/actionmetadata.go @@ -0,0 +1,19 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package toproto6 + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/internal/fwserver" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" +) + +// ActionMetadata returns the tfprotov6.ActionMetadata for a +// fwserver.ActionMetadata. +func ActionMetadata(ctx context.Context, fw fwserver.ActionMetadata) tfprotov6.ActionMetadata { + return tfprotov6.ActionMetadata{ + TypeName: fw.TypeName, + } +} diff --git a/internal/toproto6/actionmetadata_test.go b/internal/toproto6/actionmetadata_test.go new file mode 100644 index 000000000..d28e02dde --- /dev/null +++ b/internal/toproto6/actionmetadata_test.go @@ -0,0 +1,44 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package toproto6_test + +import ( + "context" + "testing" + + "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/internal/fwserver" + "github.com/hashicorp/terraform-plugin-framework/internal/toproto6" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" +) + +func TestActionMetadata(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + fw fwserver.ActionMetadata + expected tfprotov6.ActionMetadata + }{ + "TypeName": { + fw: fwserver.ActionMetadata{ + TypeName: "test", + }, + expected: tfprotov6.ActionMetadata{ + TypeName: "test", + }, + }, + } + + for name, testCase := range testCases { + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := toproto6.ActionMetadata(context.Background(), testCase.fw) + + if diff := cmp.Diff(got, testCase.expected); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} diff --git a/internal/toproto6/getmetadata.go b/internal/toproto6/getmetadata.go index bf235fe21..faedce552 100644 --- a/internal/toproto6/getmetadata.go +++ b/internal/toproto6/getmetadata.go @@ -18,6 +18,7 @@ func GetMetadataResponse(ctx context.Context, fw *fwserver.GetMetadataResponse) } protov6 := &tfprotov6.GetMetadataResponse{ + Actions: make([]tfprotov6.ActionMetadata, 0, len(fw.Actions)), DataSources: make([]tfprotov6.DataSourceMetadata, 0, len(fw.DataSources)), Diagnostics: Diagnostics(ctx, fw.Diagnostics), EphemeralResources: make([]tfprotov6.EphemeralResourceMetadata, 0, len(fw.EphemeralResources)), @@ -27,6 +28,10 @@ func GetMetadataResponse(ctx context.Context, fw *fwserver.GetMetadataResponse) ServerCapabilities: ServerCapabilities(ctx, fw.ServerCapabilities), } + for _, action := range fw.Actions { + protov6.Actions = append(protov6.Actions, ActionMetadata(ctx, action)) + } + for _, datasource := range fw.DataSources { protov6.DataSources = append(protov6.DataSources, DataSourceMetadata(ctx, datasource)) } diff --git a/internal/toproto6/getmetadata_test.go b/internal/toproto6/getmetadata_test.go index ee601823d..e4877f12e 100644 --- a/internal/toproto6/getmetadata_test.go +++ b/internal/toproto6/getmetadata_test.go @@ -25,6 +25,33 @@ func TestGetMetadataResponse(t *testing.T) { input: nil, expected: nil, }, + "actions": { + input: &fwserver.GetMetadataResponse{ + Actions: []fwserver.ActionMetadata{ + { + TypeName: "test_action_1", + }, + { + TypeName: "test_action_2", + }, + }, + }, + expected: &tfprotov6.GetMetadataResponse{ + Actions: []tfprotov6.ActionMetadata{ + { + TypeName: "test_action_1", + }, + { + TypeName: "test_action_2", + }, + }, + DataSources: []tfprotov6.DataSourceMetadata{}, + EphemeralResources: []tfprotov6.EphemeralResourceMetadata{}, + Functions: []tfprotov6.FunctionMetadata{}, + ListResources: []tfprotov6.ListResourceMetadata{}, + Resources: []tfprotov6.ResourceMetadata{}, + }, + }, "datasources": { input: &fwserver.GetMetadataResponse{ DataSources: []fwserver.DataSourceMetadata{ @@ -37,6 +64,7 @@ func TestGetMetadataResponse(t *testing.T) { }, }, expected: &tfprotov6.GetMetadataResponse{ + Actions: []tfprotov6.ActionMetadata{}, DataSources: []tfprotov6.DataSourceMetadata{ { TypeName: "test_data_source_1", @@ -63,6 +91,7 @@ func TestGetMetadataResponse(t *testing.T) { }, }, expected: &tfprotov6.GetMetadataResponse{ + Actions: []tfprotov6.ActionMetadata{}, DataSources: []tfprotov6.DataSourceMetadata{}, Diagnostics: []*tfprotov6.Diagnostic{ { @@ -91,6 +120,7 @@ func TestGetMetadataResponse(t *testing.T) { }, }, expected: &tfprotov6.GetMetadataResponse{ + Actions: []tfprotov6.ActionMetadata{}, DataSources: []tfprotov6.DataSourceMetadata{}, EphemeralResources: []tfprotov6.EphemeralResourceMetadata{ { @@ -117,6 +147,7 @@ func TestGetMetadataResponse(t *testing.T) { }, }, expected: &tfprotov6.GetMetadataResponse{ + Actions: []tfprotov6.ActionMetadata{}, DataSources: []tfprotov6.DataSourceMetadata{}, EphemeralResources: []tfprotov6.EphemeralResourceMetadata{}, Functions: []tfprotov6.FunctionMetadata{ @@ -143,6 +174,7 @@ func TestGetMetadataResponse(t *testing.T) { }, }, expected: &tfprotov6.GetMetadataResponse{ + Actions: []tfprotov6.ActionMetadata{}, DataSources: []tfprotov6.DataSourceMetadata{}, EphemeralResources: []tfprotov6.EphemeralResourceMetadata{}, Functions: []tfprotov6.FunctionMetadata{}, @@ -169,6 +201,7 @@ func TestGetMetadataResponse(t *testing.T) { }, }, expected: &tfprotov6.GetMetadataResponse{ + Actions: []tfprotov6.ActionMetadata{}, DataSources: []tfprotov6.DataSourceMetadata{}, EphemeralResources: []tfprotov6.EphemeralResourceMetadata{}, Functions: []tfprotov6.FunctionMetadata{}, @@ -191,6 +224,7 @@ func TestGetMetadataResponse(t *testing.T) { }, }, expected: &tfprotov6.GetMetadataResponse{ + Actions: []tfprotov6.ActionMetadata{}, DataSources: []tfprotov6.DataSourceMetadata{}, EphemeralResources: []tfprotov6.EphemeralResourceMetadata{}, Functions: []tfprotov6.FunctionMetadata{}, diff --git a/internal/toproto6/getproviderschema.go b/internal/toproto6/getproviderschema.go index e72a9d612..4fe9de985 100644 --- a/internal/toproto6/getproviderschema.go +++ b/internal/toproto6/getproviderschema.go @@ -18,6 +18,7 @@ func GetProviderSchemaResponse(ctx context.Context, fw *fwserver.GetProviderSche } protov6 := &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: make(map[string]*tfprotov6.ActionSchema, len(fw.ActionSchemas)), DataSourceSchemas: make(map[string]*tfprotov6.Schema, len(fw.DataSourceSchemas)), Diagnostics: Diagnostics(ctx, fw.Diagnostics), EphemeralResourceSchemas: make(map[string]*tfprotov6.Schema, len(fw.EphemeralResourceSchemas)), @@ -53,6 +54,20 @@ func GetProviderSchemaResponse(ctx context.Context, fw *fwserver.GetProviderSche return protov6 } + for actionType, actionSchema := range fw.ActionSchemas { + protov6.ActionSchemas[actionType], err = ActionSchema(ctx, actionSchema) + + if err != nil { + protov6.Diagnostics = append(protov6.Diagnostics, &tfprotov6.Diagnostic{ + Severity: tfprotov6.DiagnosticSeverityError, + Summary: "Error converting action schema", + Detail: "The schema for the action \"" + actionType + "\" couldn't be converted into a usable type. This is always a problem with the provider. Please report the following to the provider developer:\n\n" + err.Error(), + }) + + return protov6 + } + } + for dataSourceType, dataSourceSchema := range fw.DataSourceSchemas { protov6.DataSourceSchemas[dataSourceType], err = Schema(ctx, dataSourceSchema) diff --git a/internal/toproto6/getproviderschema_test.go b/internal/toproto6/getproviderschema_test.go index ed57d5f97..5c0037b82 100644 --- a/internal/toproto6/getproviderschema_test.go +++ b/internal/toproto6/getproviderschema_test.go @@ -11,6 +11,7 @@ import ( "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-go/tftypes" + actionschema "github.com/hashicorp/terraform-plugin-framework/action/schema" "github.com/hashicorp/terraform-plugin-framework/attr" datasourceschema "github.com/hashicorp/terraform-plugin-framework/datasource/schema" ephemeralschema "github.com/hashicorp/terraform-plugin-framework/ephemeral/schema" @@ -38,6 +39,118 @@ func TestGetProviderSchemaResponse(t *testing.T) { input: nil, expected: nil, }, + "action-multiple-actions": { + input: &fwserver.GetProviderSchemaResponse{ + ActionSchemas: map[string]actionschema.SchemaType{ + "test_action_1": actionschema.UnlinkedSchema{ + Attributes: map[string]actionschema.Attribute{ + "test_attribute": actionschema.StringAttribute{ + Required: true, + }, + }, + }, + "test_action_2": actionschema.UnlinkedSchema{ + Attributes: map[string]actionschema.Attribute{ + "test_attribute": actionschema.StringAttribute{ + Optional: true, + DeprecationMessage: "deprecated", + }, + }, + }, + }, + }, + expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{ + "test_action_1": { + Type: tfprotov6.UnlinkedActionSchemaType{}, + Schema: &tfprotov6.Schema{ + Version: 0, + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "test_attribute", + Type: tftypes.String, + Required: true, + }, + }, + }, + }, + }, + "test_action_2": { + Type: tfprotov6.UnlinkedActionSchemaType{}, + Schema: &tfprotov6.Schema{ + Version: 0, + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "test_attribute", + Type: tftypes.String, + Optional: true, + Deprecated: true, + }, + }, + }, + }, + }, + }, + DataSourceSchemas: map[string]*tfprotov6.Schema{}, + EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, + Functions: map[string]*tfprotov6.Function{}, + ListResourceSchemas: map[string]*tfprotov6.Schema{}, + ResourceSchemas: map[string]*tfprotov6.Schema{}, + }, + }, + "action-type-nested-attributes": { + input: &fwserver.GetProviderSchemaResponse{ + ActionSchemas: map[string]actionschema.SchemaType{ + "test_action": actionschema.UnlinkedSchema{ + Attributes: map[string]actionschema.Attribute{ + "test_attribute": actionschema.SingleNestedAttribute{ + Attributes: map[string]actionschema.Attribute{ + "test_nested_attribute": actionschema.StringAttribute{ + Required: true, + }, + }, + Required: true, + }, + }, + }, + }, + }, + expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{ + "test_action": { + Type: tfprotov6.UnlinkedActionSchemaType{}, + Schema: &tfprotov6.Schema{ + Version: 0, + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "test_attribute", + NestedType: &tfprotov6.SchemaObject{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "test_nested_attribute", + Type: tftypes.String, + Required: true, + }, + }, + Nesting: tfprotov6.SchemaObjectNestingModeSingle, + }, + Required: true, + }, + }, + }, + }, + }, + }, + DataSourceSchemas: map[string]*tfprotov6.Schema{}, + EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, + Functions: map[string]*tfprotov6.Function{}, + ListResourceSchemas: map[string]*tfprotov6.Schema{}, + ResourceSchemas: map[string]*tfprotov6.Schema{}, + }, + }, "data-source-multiple-data-sources": { input: &fwserver.GetProviderSchemaResponse{ DataSourceSchemas: map[string]fwschema.Schema{ @@ -58,6 +171,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{ "test_data_source_1": { Block: &tfprotov6.SchemaBlock{ @@ -101,6 +215,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{ "test_data_source": { Block: &tfprotov6.SchemaBlock{ @@ -134,6 +249,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{ "test_data_source": { Block: &tfprotov6.SchemaBlock{ @@ -167,6 +283,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{ "test_data_source": { Block: &tfprotov6.SchemaBlock{ @@ -200,6 +317,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{ "test_data_source": { Block: &tfprotov6.SchemaBlock{ @@ -233,6 +351,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{ "test_data_source": { Block: &tfprotov6.SchemaBlock{ @@ -266,6 +385,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{ "test_data_source": { Block: &tfprotov6.SchemaBlock{ @@ -299,6 +419,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{ "test_data_source": { Block: &tfprotov6.SchemaBlock{ @@ -332,6 +453,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{ "test_data_source": { Block: &tfprotov6.SchemaBlock{ @@ -364,6 +486,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{ "test_data_source": { Block: &tfprotov6.SchemaBlock{ @@ -396,6 +519,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{ "test_data_source": { Block: &tfprotov6.SchemaBlock{ @@ -428,6 +552,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{ "test_data_source": { Block: &tfprotov6.SchemaBlock{ @@ -463,6 +588,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{ "test_data_source": { Block: &tfprotov6.SchemaBlock{ @@ -506,6 +632,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{ "test_data_source": { Block: &tfprotov6.SchemaBlock{ @@ -552,6 +679,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{ "test_data_source": { Block: &tfprotov6.SchemaBlock{ @@ -591,6 +719,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{ "test_data_source": { Block: &tfprotov6.SchemaBlock{ @@ -632,6 +761,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{ "test_data_source": { Block: &tfprotov6.SchemaBlock{ @@ -674,6 +804,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{ "test_data_source": { Block: &tfprotov6.SchemaBlock{ @@ -708,6 +839,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{ "test_data_source": { Block: &tfprotov6.SchemaBlock{ @@ -743,6 +875,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{ "test_data_source": { Block: &tfprotov6.SchemaBlock{ @@ -786,6 +919,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{ "test_data_source": { Block: &tfprotov6.SchemaBlock{ @@ -832,6 +966,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{ "test_data_source": { Block: &tfprotov6.SchemaBlock{ @@ -873,6 +1008,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{ "test_data_source": { Block: &tfprotov6.SchemaBlock{ @@ -910,6 +1046,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{ "test_data_source": { Block: &tfprotov6.SchemaBlock{ @@ -949,6 +1086,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{ "test_data_source": { Block: &tfprotov6.SchemaBlock{ @@ -990,6 +1128,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{ "test_data_source": { Block: &tfprotov6.SchemaBlock{ @@ -1022,6 +1161,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{ "test_data_source": { Block: &tfprotov6.SchemaBlock{ @@ -1060,6 +1200,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{ "test_data_source": { Block: &tfprotov6.SchemaBlock{ @@ -1106,6 +1247,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{ "test_data_source": { Block: &tfprotov6.SchemaBlock{ @@ -1150,6 +1292,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{ "test_data_source": { Block: &tfprotov6.SchemaBlock{ @@ -1197,6 +1340,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{ "test_ephemeral_resource_1": { @@ -1240,6 +1384,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{ "test_ephemeral_resource": { @@ -1273,6 +1418,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{ "test_ephemeral_resource": { @@ -1306,6 +1452,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{ "test_ephemeral_resource": { @@ -1339,6 +1486,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{ "test_ephemeral_resource": { @@ -1372,6 +1520,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{ "test_ephemeral_resource": { @@ -1405,6 +1554,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{ "test_ephemeral_resource": { @@ -1438,6 +1588,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{ "test_ephemeral_resource": { @@ -1470,6 +1621,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{ "test_ephemeral_resource": { @@ -1502,6 +1654,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{ "test_ephemeral_resource": { @@ -1534,6 +1687,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{ "test_ephemeral_resource": { @@ -1566,6 +1720,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{ "test_ephemeral_resource": { @@ -1601,6 +1756,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{ "test_ephemeral_resource": { @@ -1644,6 +1800,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{ "test_ephemeral_resource": { @@ -1690,6 +1847,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{ "test_ephemeral_resource": { @@ -1729,6 +1887,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{ "test_ephemeral_resource": { @@ -1770,6 +1929,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{ "test_ephemeral_resource": { @@ -1812,6 +1972,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{ "test_ephemeral_resource": { @@ -1846,6 +2007,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{ "test_ephemeral_resource": { @@ -1881,6 +2043,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{ "test_ephemeral_resource": { @@ -1924,6 +2087,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{ "test_ephemeral_resource": { @@ -1970,6 +2134,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{ "test_ephemeral_resource": { @@ -2011,6 +2176,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{ "test_ephemeral_resource": { @@ -2048,6 +2214,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{ "test_ephemeral_resource": { @@ -2087,6 +2254,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{ "test_ephemeral_resource": { @@ -2128,6 +2296,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{ "test_ephemeral_resource": { @@ -2160,6 +2329,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{ "test_ephemeral_resource": { @@ -2198,6 +2368,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{ "test_ephemeral_resource": { @@ -2244,6 +2415,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{ "test_ephemeral_resource": { @@ -2288,6 +2460,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{ "test_ephemeral_resource": { @@ -2327,6 +2500,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{ @@ -2357,6 +2531,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{ @@ -2382,6 +2557,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{ @@ -2411,6 +2587,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{ @@ -2444,6 +2621,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{ @@ -2468,6 +2646,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{ @@ -2493,6 +2672,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{ @@ -2530,6 +2710,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -2574,6 +2755,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -2607,6 +2789,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -2639,6 +2822,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -2671,6 +2855,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -2702,6 +2887,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -2732,6 +2918,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -2761,6 +2948,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -2791,6 +2979,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -2821,6 +3010,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -2851,6 +3041,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -2880,6 +3071,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -2909,6 +3101,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -2938,6 +3131,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -2970,6 +3164,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -3010,6 +3205,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -3053,6 +3249,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -3089,6 +3286,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -3127,6 +3325,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -3166,6 +3365,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -3197,6 +3397,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -3229,6 +3430,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -3269,6 +3471,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -3312,6 +3515,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -3350,6 +3554,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -3384,6 +3589,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -3420,6 +3626,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -3458,6 +3665,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -3487,6 +3695,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -3522,6 +3731,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -3565,6 +3775,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -3606,6 +3817,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -3643,6 +3855,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -3672,6 +3885,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -3701,6 +3915,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -3730,6 +3945,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -3759,6 +3975,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -3791,6 +4008,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -3831,6 +4049,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -3874,6 +4093,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -3910,6 +4130,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -3948,6 +4169,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -3987,6 +4209,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -4018,6 +4241,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -4050,6 +4274,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -4090,6 +4315,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -4133,6 +4359,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -4171,6 +4398,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -4205,6 +4433,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -4241,6 +4470,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -4279,6 +4509,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -4317,6 +4548,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -4360,6 +4592,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -4393,6 +4626,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -4426,6 +4660,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -4459,6 +4694,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -4492,6 +4728,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -4525,6 +4762,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -4559,6 +4797,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -4592,6 +4831,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -4624,6 +4864,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -4656,6 +4897,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -4688,6 +4930,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -4723,6 +4966,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -4766,6 +5010,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -4812,6 +5057,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -4851,6 +5097,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -4892,6 +5139,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -4934,6 +5182,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -4968,6 +5217,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -5003,6 +5253,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -5046,6 +5297,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -5092,6 +5344,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -5133,6 +5386,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -5170,6 +5424,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -5209,6 +5464,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -5250,6 +5506,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -5282,6 +5539,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -5320,6 +5578,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -5366,6 +5625,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -5410,6 +5670,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -5446,6 +5707,7 @@ func TestGetProviderSchemaResponse(t *testing.T) { }, }, expected: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, From 32b20839418a691e4d3025b2914d6924599ec0e2 Mon Sep 17 00:00:00 2001 From: Austin Valle Date: Thu, 10 Jul 2025 16:55:49 -0400 Subject: [PATCH 7/8] the rest of the tests --- internal/fwserver/server_actions.go | 4 +- .../proto5server/server_getmetadata_test.go | 503 +---------- .../server_getproviderschema_test.go | 821 +++--------------- .../proto6server/server_getmetadata_test.go | 503 +---------- .../server_getproviderschema_test.go | 821 +++--------------- 5 files changed, 338 insertions(+), 2314 deletions(-) diff --git a/internal/fwserver/server_actions.go b/internal/fwserver/server_actions.go index 13d2e381f..4fd25ae87 100644 --- a/internal/fwserver/server_actions.go +++ b/internal/fwserver/server_actions.go @@ -53,9 +53,9 @@ func (s *Server) ActionFuncs(ctx context.Context) (map[string]func() action.Acti return s.actionFuncs, s.actionFuncsDiags } - logging.FrameworkTrace(ctx, "Calling provider defined Provider Actions") + logging.FrameworkTrace(ctx, "Calling provider defined Actions") actionFuncsSlice := provider.Actions(ctx) - logging.FrameworkTrace(ctx, "Called provider defined Provider Actions") + logging.FrameworkTrace(ctx, "Called provider defined Actions") for _, actionFunc := range actionFuncsSlice { actionImpl := actionFunc() diff --git a/internal/proto5server/server_getmetadata_test.go b/internal/proto5server/server_getmetadata_test.go index 01e0dc43c..6f28652f4 100644 --- a/internal/proto5server/server_getmetadata_test.go +++ b/internal/proto5server/server_getmetadata_test.go @@ -9,6 +9,7 @@ import ( "testing" "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/action" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/ephemeral" "github.com/hashicorp/terraform-plugin-framework/function" @@ -28,23 +29,23 @@ func TestServerGetMetadata(t *testing.T) { expectedError error expectedResponse *tfprotov5.GetMetadataResponse }{ - "datasources": { + "actions": { server: &Server{ FrameworkServer: fwserver.Server{ Provider: &testprovider.Provider{ - DataSourcesMethod: func(_ context.Context) []func() datasource.DataSource { - return []func() datasource.DataSource{ - func() datasource.DataSource { - return &testprovider.DataSource{ - MetadataMethod: func(_ context.Context, _ datasource.MetadataRequest, resp *datasource.MetadataResponse) { - resp.TypeName = "test_data_source1" + ActionsMethod: func(_ context.Context) []func() action.Action { + return []func() action.Action{ + func() action.Action { + return &testprovider.Action{ + MetadataMethod: func(_ context.Context, _ action.MetadataRequest, resp *action.MetadataResponse) { + resp.TypeName = "test_action1" }, } }, - func() datasource.DataSource { - return &testprovider.DataSource{ - MetadataMethod: func(_ context.Context, _ datasource.MetadataRequest, resp *datasource.MetadataResponse) { - resp.TypeName = "test_data_source2" + func() action.Action { + return &testprovider.Action{ + MetadataMethod: func(_ context.Context, _ action.MetadataRequest, resp *action.MetadataResponse) { + resp.TypeName = "test_action2" }, } }, @@ -55,14 +56,15 @@ func TestServerGetMetadata(t *testing.T) { }, request: &tfprotov5.GetMetadataRequest{}, expectedResponse: &tfprotov5.GetMetadataResponse{ - DataSources: []tfprotov5.DataSourceMetadata{ + Actions: []tfprotov5.ActionMetadata{ { - TypeName: "test_data_source1", + TypeName: "test_action1", }, { - TypeName: "test_data_source2", + TypeName: "test_action2", }, }, + DataSources: []tfprotov5.DataSourceMetadata{}, EphemeralResources: []tfprotov5.EphemeralResourceMetadata{}, Functions: []tfprotov5.FunctionMetadata{}, ListResources: []tfprotov5.ListResourceMetadata{}, @@ -74,7 +76,7 @@ func TestServerGetMetadata(t *testing.T) { }, }, }, - "datasources-duplicate-type-name": { + "datasources": { server: &Server{ FrameworkServer: fwserver.Server{ Provider: &testprovider.Provider{ @@ -83,14 +85,14 @@ func TestServerGetMetadata(t *testing.T) { func() datasource.DataSource { return &testprovider.DataSource{ MetadataMethod: func(_ context.Context, _ datasource.MetadataRequest, resp *datasource.MetadataResponse) { - resp.TypeName = "test_data_source" + resp.TypeName = "test_data_source1" }, } }, func() datasource.DataSource { return &testprovider.DataSource{ MetadataMethod: func(_ context.Context, _ datasource.MetadataRequest, resp *datasource.MetadataResponse) { - resp.TypeName = "test_data_source" + resp.TypeName = "test_data_source2" }, } }, @@ -101,54 +103,13 @@ func TestServerGetMetadata(t *testing.T) { }, request: &tfprotov5.GetMetadataRequest{}, expectedResponse: &tfprotov5.GetMetadataResponse{ - DataSources: []tfprotov5.DataSourceMetadata{}, - Diagnostics: []*tfprotov5.Diagnostic{ + Actions: []tfprotov5.ActionMetadata{}, + DataSources: []tfprotov5.DataSourceMetadata{ { - Severity: tfprotov5.DiagnosticSeverityError, - Summary: "Duplicate Data Source Type Defined", - Detail: "The test_data_source data source type name was returned for multiple data sources. " + - "Data source type names must be unique. " + - "This is always an issue with the provider and should be reported to the provider developers.", - }, - }, - EphemeralResources: []tfprotov5.EphemeralResourceMetadata{}, - Functions: []tfprotov5.FunctionMetadata{}, - ListResources: []tfprotov5.ListResourceMetadata{}, - Resources: []tfprotov5.ResourceMetadata{}, - ServerCapabilities: &tfprotov5.ServerCapabilities{ - GetProviderSchemaOptional: true, - MoveResourceState: true, - PlanDestroy: true, - }, - }, - }, - "datasources-empty-type-name": { - server: &Server{ - FrameworkServer: fwserver.Server{ - Provider: &testprovider.Provider{ - DataSourcesMethod: func(_ context.Context) []func() datasource.DataSource { - return []func() datasource.DataSource{ - func() datasource.DataSource { - return &testprovider.DataSource{ - MetadataMethod: func(_ context.Context, _ datasource.MetadataRequest, resp *datasource.MetadataResponse) { - resp.TypeName = "" - }, - } - }, - } - }, + TypeName: "test_data_source1", }, - }, - }, - request: &tfprotov5.GetMetadataRequest{}, - expectedResponse: &tfprotov5.GetMetadataResponse{ - DataSources: []tfprotov5.DataSourceMetadata{}, - Diagnostics: []*tfprotov5.Diagnostic{ { - Severity: tfprotov5.DiagnosticSeverityError, - Summary: "Data Source Type Name Missing", - Detail: "The *testprovider.DataSource DataSource returned an empty string from the Metadata method. " + - "This is always an issue with the provider and should be reported to the provider developers.", + TypeName: "test_data_source2", }, }, EphemeralResources: []tfprotov5.EphemeralResourceMetadata{}, @@ -189,6 +150,7 @@ func TestServerGetMetadata(t *testing.T) { }, request: &tfprotov5.GetMetadataRequest{}, expectedResponse: &tfprotov5.GetMetadataResponse{ + Actions: []tfprotov5.ActionMetadata{}, DataSources: []tfprotov5.DataSourceMetadata{}, Functions: []tfprotov5.FunctionMetadata{}, EphemeralResources: []tfprotov5.EphemeralResourceMetadata{ @@ -208,94 +170,6 @@ func TestServerGetMetadata(t *testing.T) { }, }, }, - "ephemeralresources-duplicate-type-name": { - server: &Server{ - FrameworkServer: fwserver.Server{ - Provider: &testprovider.Provider{ - EphemeralResourcesMethod: func(_ context.Context) []func() ephemeral.EphemeralResource { - return []func() ephemeral.EphemeralResource{ - func() ephemeral.EphemeralResource { - return &testprovider.EphemeralResource{ - MetadataMethod: func(_ context.Context, _ ephemeral.MetadataRequest, resp *ephemeral.MetadataResponse) { - resp.TypeName = "test_ephemeral_resource" - }, - } - }, - func() ephemeral.EphemeralResource { - return &testprovider.EphemeralResource{ - MetadataMethod: func(_ context.Context, _ ephemeral.MetadataRequest, resp *ephemeral.MetadataResponse) { - resp.TypeName = "test_ephemeral_resource" - }, - } - }, - } - }, - }, - }, - }, - request: &tfprotov5.GetMetadataRequest{}, - expectedResponse: &tfprotov5.GetMetadataResponse{ - DataSources: []tfprotov5.DataSourceMetadata{}, - Diagnostics: []*tfprotov5.Diagnostic{ - { - Severity: tfprotov5.DiagnosticSeverityError, - Summary: "Duplicate Ephemeral Resource Type Defined", - Detail: "The test_ephemeral_resource ephemeral resource type name was returned for multiple ephemeral resources. " + - "Ephemeral resource type names must be unique. " + - "This is always an issue with the provider and should be reported to the provider developers.", - }, - }, - EphemeralResources: []tfprotov5.EphemeralResourceMetadata{}, - Functions: []tfprotov5.FunctionMetadata{}, - ListResources: []tfprotov5.ListResourceMetadata{}, - Resources: []tfprotov5.ResourceMetadata{}, - ServerCapabilities: &tfprotov5.ServerCapabilities{ - GetProviderSchemaOptional: true, - MoveResourceState: true, - PlanDestroy: true, - }, - }, - }, - "ephemeralresources-empty-type-name": { - server: &Server{ - FrameworkServer: fwserver.Server{ - Provider: &testprovider.Provider{ - EphemeralResourcesMethod: func(_ context.Context) []func() ephemeral.EphemeralResource { - return []func() ephemeral.EphemeralResource{ - func() ephemeral.EphemeralResource { - return &testprovider.EphemeralResource{ - MetadataMethod: func(_ context.Context, _ ephemeral.MetadataRequest, resp *ephemeral.MetadataResponse) { - resp.TypeName = "" - }, - } - }, - } - }, - }, - }, - }, - request: &tfprotov5.GetMetadataRequest{}, - expectedResponse: &tfprotov5.GetMetadataResponse{ - DataSources: []tfprotov5.DataSourceMetadata{}, - Diagnostics: []*tfprotov5.Diagnostic{ - { - Severity: tfprotov5.DiagnosticSeverityError, - Summary: "Ephemeral Resource Type Name Missing", - Detail: "The *testprovider.EphemeralResource EphemeralResource returned an empty string from the Metadata method. " + - "This is always an issue with the provider and should be reported to the provider developers.", - }, - }, - EphemeralResources: []tfprotov5.EphemeralResourceMetadata{}, - Functions: []tfprotov5.FunctionMetadata{}, - ListResources: []tfprotov5.ListResourceMetadata{}, - Resources: []tfprotov5.ResourceMetadata{}, - ServerCapabilities: &tfprotov5.ServerCapabilities{ - GetProviderSchemaOptional: true, - MoveResourceState: true, - PlanDestroy: true, - }, - }, - }, "functions": { server: &Server{ FrameworkServer: fwserver.Server{ @@ -323,6 +197,7 @@ func TestServerGetMetadata(t *testing.T) { }, request: &tfprotov5.GetMetadataRequest{}, expectedResponse: &tfprotov5.GetMetadataResponse{ + Actions: []tfprotov5.ActionMetadata{}, DataSources: []tfprotov5.DataSourceMetadata{}, Functions: []tfprotov5.FunctionMetadata{ { @@ -342,94 +217,6 @@ func TestServerGetMetadata(t *testing.T) { }, }, }, - "functions-duplicate-type-name": { - server: &Server{ - FrameworkServer: fwserver.Server{ - Provider: &testprovider.ProviderWithFunctions{ - FunctionsMethod: func(_ context.Context) []func() function.Function { - return []func() function.Function{ - func() function.Function { - return &testprovider.Function{ - MetadataMethod: func(_ context.Context, _ function.MetadataRequest, resp *function.MetadataResponse) { - resp.Name = "testfunction" // intentionally duplicate - }, - } - }, - func() function.Function { - return &testprovider.Function{ - MetadataMethod: func(_ context.Context, _ function.MetadataRequest, resp *function.MetadataResponse) { - resp.Name = "testfunction" // intentionally duplicate - }, - } - }, - } - }, - }, - }, - }, - request: &tfprotov5.GetMetadataRequest{}, - expectedResponse: &tfprotov5.GetMetadataResponse{ - DataSources: []tfprotov5.DataSourceMetadata{}, - Diagnostics: []*tfprotov5.Diagnostic{ - { - Severity: tfprotov5.DiagnosticSeverityError, - Summary: "Duplicate Function Name Defined", - Detail: "The testfunction function name was returned for multiple functions. " + - "Function names must be unique. " + - "This is always an issue with the provider and should be reported to the provider developers.", - }, - }, - EphemeralResources: []tfprotov5.EphemeralResourceMetadata{}, - Functions: []tfprotov5.FunctionMetadata{}, - ListResources: []tfprotov5.ListResourceMetadata{}, - Resources: []tfprotov5.ResourceMetadata{}, - ServerCapabilities: &tfprotov5.ServerCapabilities{ - GetProviderSchemaOptional: true, - MoveResourceState: true, - PlanDestroy: true, - }, - }, - }, - "functions-empty-type-name": { - server: &Server{ - FrameworkServer: fwserver.Server{ - Provider: &testprovider.ProviderWithFunctions{ - FunctionsMethod: func(_ context.Context) []func() function.Function { - return []func() function.Function{ - func() function.Function { - return &testprovider.Function{ - MetadataMethod: func(_ context.Context, _ function.MetadataRequest, resp *function.MetadataResponse) { - resp.Name = "" // intentionally empty - }, - } - }, - } - }, - }, - }, - }, - request: &tfprotov5.GetMetadataRequest{}, - expectedResponse: &tfprotov5.GetMetadataResponse{ - DataSources: []tfprotov5.DataSourceMetadata{}, - Diagnostics: []*tfprotov5.Diagnostic{ - { - Severity: tfprotov5.DiagnosticSeverityError, - Summary: "Function Name Missing", - Detail: "The *testprovider.Function Function returned an empty string from the Metadata method. " + - "This is always an issue with the provider and should be reported to the provider developers.", - }, - }, - EphemeralResources: []tfprotov5.EphemeralResourceMetadata{}, - Functions: []tfprotov5.FunctionMetadata{}, - ListResources: []tfprotov5.ListResourceMetadata{}, - Resources: []tfprotov5.ResourceMetadata{}, - ServerCapabilities: &tfprotov5.ServerCapabilities{ - GetProviderSchemaOptional: true, - MoveResourceState: true, - PlanDestroy: true, - }, - }, - }, "listresources": { server: &Server{ FrameworkServer: fwserver.Server{ @@ -475,6 +262,7 @@ func TestServerGetMetadata(t *testing.T) { }, request: &tfprotov5.GetMetadataRequest{}, expectedResponse: &tfprotov5.GetMetadataResponse{ + Actions: []tfprotov5.ActionMetadata{}, DataSources: []tfprotov5.DataSourceMetadata{}, Functions: []tfprotov5.FunctionMetadata{}, EphemeralResources: []tfprotov5.EphemeralResourceMetadata{}, @@ -501,156 +289,6 @@ func TestServerGetMetadata(t *testing.T) { }, }, }, - "listresources-duplicate-type-name": { - server: &Server{ - FrameworkServer: fwserver.Server{ - Provider: &testprovider.Provider{ - ListResourcesMethod: func(_ context.Context) []func() list.ListResource { - return []func() list.ListResource{ - func() list.ListResource { - return &testprovider.ListResource{ - MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "test_list_resource" - }, - } - }, - func() list.ListResource { - return &testprovider.ListResource{ - MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "test_list_resource" - }, - } - }, - } - }, - ResourcesMethod: func(_ context.Context) []func() resource.Resource { - return []func() resource.Resource{ - func() resource.Resource { - return &testprovider.Resource{ - MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "test_list_resource" - }, - } - }, - } - }, - }, - }, - }, - request: &tfprotov5.GetMetadataRequest{}, - expectedResponse: &tfprotov5.GetMetadataResponse{ - DataSources: []tfprotov5.DataSourceMetadata{}, - Diagnostics: []*tfprotov5.Diagnostic{ - { - Severity: tfprotov5.DiagnosticSeverityError, - Summary: "Duplicate ListResource Type Defined", - Detail: "The test_list_resource ListResource type name was returned for multiple list resources. " + - "ListResource type names must be unique. " + - "This is always an issue with the provider and should be reported to the provider developers.", - }, - }, - EphemeralResources: []tfprotov5.EphemeralResourceMetadata{}, - Functions: []tfprotov5.FunctionMetadata{}, - ListResources: []tfprotov5.ListResourceMetadata{}, - Resources: []tfprotov5.ResourceMetadata{}, - ServerCapabilities: &tfprotov5.ServerCapabilities{ - GetProviderSchemaOptional: true, - MoveResourceState: true, - PlanDestroy: true, - }, - }, - }, - "listresources-empty-type-name": { - server: &Server{ - FrameworkServer: fwserver.Server{ - Provider: &testprovider.Provider{ - ListResourcesMethod: func(_ context.Context) []func() list.ListResource { - return []func() list.ListResource{ - func() list.ListResource { - return &testprovider.ListResource{ - MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "" - }, - } - }, - } - }, - ResourcesMethod: func(_ context.Context) []func() resource.Resource { - return []func() resource.Resource{ - func() resource.Resource { - return &testprovider.Resource{ - MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "test_list_resource" - }, - } - }, - } - }, - }, - }, - }, - request: &tfprotov5.GetMetadataRequest{}, - expectedResponse: &tfprotov5.GetMetadataResponse{ - DataSources: []tfprotov5.DataSourceMetadata{}, - Diagnostics: []*tfprotov5.Diagnostic{ - { - Severity: tfprotov5.DiagnosticSeverityError, - Summary: "ListResource Type Name Missing", - Detail: "The *testprovider.ListResource ListResource returned an empty string from the Metadata method. " + - "This is always an issue with the provider and should be reported to the provider developers.", - }, - }, - EphemeralResources: []tfprotov5.EphemeralResourceMetadata{}, - Functions: []tfprotov5.FunctionMetadata{}, - ListResources: []tfprotov5.ListResourceMetadata{}, - Resources: []tfprotov5.ResourceMetadata{}, - ServerCapabilities: &tfprotov5.ServerCapabilities{ - GetProviderSchemaOptional: true, - MoveResourceState: true, - PlanDestroy: true, - }, - }, - }, - "listresources-missing-resource-definition": { - server: &Server{ - FrameworkServer: fwserver.Server{ - Provider: &testprovider.Provider{ - ListResourcesMethod: func(_ context.Context) []func() list.ListResource { - return []func() list.ListResource{ - func() list.ListResource { - return &testprovider.ListResource{ - MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "test_list_resource" - }, - } - }, - } - }, - }, - }, - }, - request: &tfprotov5.GetMetadataRequest{}, - expectedResponse: &tfprotov5.GetMetadataResponse{ - DataSources: []tfprotov5.DataSourceMetadata{}, - Diagnostics: []*tfprotov5.Diagnostic{ - { - Severity: tfprotov5.DiagnosticSeverityError, - Summary: "ListResource Type Defined without a Matching Managed Resource Type", - Detail: "The test_list_resource ListResource type name was returned, but no matching managed Resource type was defined. " + - "This is always an issue with the provider and should be reported to the provider developers.", - }, - }, - EphemeralResources: []tfprotov5.EphemeralResourceMetadata{}, - Functions: []tfprotov5.FunctionMetadata{}, - ListResources: []tfprotov5.ListResourceMetadata{}, - Resources: []tfprotov5.ResourceMetadata{}, - ServerCapabilities: &tfprotov5.ServerCapabilities{ - GetProviderSchemaOptional: true, - MoveResourceState: true, - PlanDestroy: true, - }, - }, - }, "resources": { server: &Server{ FrameworkServer: fwserver.Server{ @@ -678,6 +316,7 @@ func TestServerGetMetadata(t *testing.T) { }, request: &tfprotov5.GetMetadataRequest{}, expectedResponse: &tfprotov5.GetMetadataResponse{ + Actions: []tfprotov5.ActionMetadata{}, DataSources: []tfprotov5.DataSourceMetadata{}, EphemeralResources: []tfprotov5.EphemeralResourceMetadata{}, Functions: []tfprotov5.FunctionMetadata{}, @@ -697,94 +336,6 @@ func TestServerGetMetadata(t *testing.T) { }, }, }, - "resources-duplicate-type-name": { - server: &Server{ - FrameworkServer: fwserver.Server{ - Provider: &testprovider.Provider{ - ResourcesMethod: func(_ context.Context) []func() resource.Resource { - return []func() resource.Resource{ - func() resource.Resource { - return &testprovider.Resource{ - MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "test_resource" - }, - } - }, - func() resource.Resource { - return &testprovider.Resource{ - MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "test_resource" - }, - } - }, - } - }, - }, - }, - }, - request: &tfprotov5.GetMetadataRequest{}, - expectedResponse: &tfprotov5.GetMetadataResponse{ - DataSources: []tfprotov5.DataSourceMetadata{}, - Diagnostics: []*tfprotov5.Diagnostic{ - { - Severity: tfprotov5.DiagnosticSeverityError, - Summary: "Duplicate Resource Type Defined", - Detail: "The test_resource resource type name was returned for multiple resources. " + - "Resource type names must be unique. " + - "This is always an issue with the provider and should be reported to the provider developers.", - }, - }, - EphemeralResources: []tfprotov5.EphemeralResourceMetadata{}, - Functions: []tfprotov5.FunctionMetadata{}, - ListResources: []tfprotov5.ListResourceMetadata{}, - Resources: []tfprotov5.ResourceMetadata{}, - ServerCapabilities: &tfprotov5.ServerCapabilities{ - GetProviderSchemaOptional: true, - MoveResourceState: true, - PlanDestroy: true, - }, - }, - }, - "resources-empty-type-name": { - server: &Server{ - FrameworkServer: fwserver.Server{ - Provider: &testprovider.Provider{ - ResourcesMethod: func(_ context.Context) []func() resource.Resource { - return []func() resource.Resource{ - func() resource.Resource { - return &testprovider.Resource{ - MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "" - }, - } - }, - } - }, - }, - }, - }, - request: &tfprotov5.GetMetadataRequest{}, - expectedResponse: &tfprotov5.GetMetadataResponse{ - DataSources: []tfprotov5.DataSourceMetadata{}, - Diagnostics: []*tfprotov5.Diagnostic{ - { - Severity: tfprotov5.DiagnosticSeverityError, - Summary: "Resource Type Name Missing", - Detail: "The *testprovider.Resource Resource returned an empty string from the Metadata method. " + - "This is always an issue with the provider and should be reported to the provider developers.", - }, - }, - EphemeralResources: []tfprotov5.EphemeralResourceMetadata{}, - Functions: []tfprotov5.FunctionMetadata{}, - ListResources: []tfprotov5.ListResourceMetadata{}, - Resources: []tfprotov5.ResourceMetadata{}, - ServerCapabilities: &tfprotov5.ServerCapabilities{ - GetProviderSchemaOptional: true, - MoveResourceState: true, - PlanDestroy: true, - }, - }, - }, } for name, testCase := range testCases { diff --git a/internal/proto5server/server_getproviderschema_test.go b/internal/proto5server/server_getproviderschema_test.go index 90e31094e..409e06e9e 100644 --- a/internal/proto5server/server_getproviderschema_test.go +++ b/internal/proto5server/server_getproviderschema_test.go @@ -9,6 +9,8 @@ import ( "testing" "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/action" + actionschema "github.com/hashicorp/terraform-plugin-framework/action/schema" "github.com/hashicorp/terraform-plugin-framework/datasource" datasourceschema "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/ephemeral" @@ -38,41 +40,41 @@ func TestServerGetProviderSchema(t *testing.T) { expectedError error expectedResponse *tfprotov5.GetProviderSchemaResponse }{ - "datasourceschemas": { + "actionschemas": { server: &Server{ FrameworkServer: fwserver.Server{ Provider: &testprovider.Provider{ - DataSourcesMethod: func(_ context.Context) []func() datasource.DataSource { - return []func() datasource.DataSource{ - func() datasource.DataSource { - return &testprovider.DataSource{ - SchemaMethod: func(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { - resp.Schema = datasourceschema.Schema{ - Attributes: map[string]datasourceschema.Attribute{ - "test1": datasourceschema.StringAttribute{ + ActionsMethod: func(_ context.Context) []func() action.Action { + return []func() action.Action{ + func() action.Action { + return &testprovider.Action{ + SchemaMethod: func(_ context.Context, _ action.SchemaRequest, resp *action.SchemaResponse) { + resp.Schema = actionschema.UnlinkedSchema{ + Attributes: map[string]actionschema.Attribute{ + "test1": actionschema.StringAttribute{ Required: true, }, }, } }, - MetadataMethod: func(_ context.Context, _ datasource.MetadataRequest, resp *datasource.MetadataResponse) { - resp.TypeName = "test_data_source1" + MetadataMethod: func(_ context.Context, _ action.MetadataRequest, resp *action.MetadataResponse) { + resp.TypeName = "test_action1" }, } }, - func() datasource.DataSource { - return &testprovider.DataSource{ - SchemaMethod: func(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { - resp.Schema = datasourceschema.Schema{ - Attributes: map[string]datasourceschema.Attribute{ - "test2": datasourceschema.StringAttribute{ + func() action.Action { + return &testprovider.Action{ + SchemaMethod: func(_ context.Context, _ action.SchemaRequest, resp *action.SchemaResponse) { + resp.Schema = actionschema.UnlinkedSchema{ + Attributes: map[string]actionschema.Attribute{ + "test2": actionschema.StringAttribute{ Required: true, }, }, } }, - MetadataMethod: func(_ context.Context, _ datasource.MetadataRequest, resp *datasource.MetadataResponse) { - resp.TypeName = "test_data_source2" + MetadataMethod: func(_ context.Context, _ action.MetadataRequest, resp *action.MetadataResponse) { + resp.TypeName = "test_action2" }, } }, @@ -83,30 +85,37 @@ func TestServerGetProviderSchema(t *testing.T) { }, request: &tfprotov5.GetProviderSchemaRequest{}, expectedResponse: &tfprotov5.GetProviderSchemaResponse{ - DataSourceSchemas: map[string]*tfprotov5.Schema{ - "test_data_source1": { - Block: &tfprotov5.SchemaBlock{ - Attributes: []*tfprotov5.SchemaAttribute{ - { - Name: "test1", - Required: true, - Type: tftypes.String, + ActionSchemas: map[string]*tfprotov5.ActionSchema{ + "test_action1": { + Type: tfprotov5.UnlinkedActionSchemaType{}, + Schema: &tfprotov5.Schema{ + Block: &tfprotov5.SchemaBlock{ + Attributes: []*tfprotov5.SchemaAttribute{ + { + Name: "test1", + Required: true, + Type: tftypes.String, + }, }, }, }, }, - "test_data_source2": { - Block: &tfprotov5.SchemaBlock{ - Attributes: []*tfprotov5.SchemaAttribute{ - { - Name: "test2", - Required: true, - Type: tftypes.String, + "test_action2": { + Type: tfprotov5.UnlinkedActionSchemaType{}, + Schema: &tfprotov5.Schema{ + Block: &tfprotov5.SchemaBlock{ + Attributes: []*tfprotov5.SchemaAttribute{ + { + Name: "test2", + Required: true, + Type: tftypes.String, + }, }, }, }, }, }, + DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, ListResourceSchemas: map[string]*tfprotov5.Schema{}, @@ -121,7 +130,7 @@ func TestServerGetProviderSchema(t *testing.T) { }, }, }, - "datasourceschemas-duplicate-type-name": { + "datasourceschemas": { server: &Server{ FrameworkServer: fwserver.Server{ Provider: &testprovider.Provider{ @@ -139,7 +148,7 @@ func TestServerGetProviderSchema(t *testing.T) { } }, MetadataMethod: func(_ context.Context, _ datasource.MetadataRequest, resp *datasource.MetadataResponse) { - resp.TypeName = "test_data_source" + resp.TypeName = "test_data_source1" }, } }, @@ -155,7 +164,7 @@ func TestServerGetProviderSchema(t *testing.T) { } }, MetadataMethod: func(_ context.Context, _ datasource.MetadataRequest, resp *datasource.MetadataResponse) { - resp.TypeName = "test_data_source" + resp.TypeName = "test_data_source2" }, } }, @@ -166,57 +175,29 @@ func TestServerGetProviderSchema(t *testing.T) { }, request: &tfprotov5.GetProviderSchemaRequest{}, expectedResponse: &tfprotov5.GetProviderSchemaResponse{ - DataSourceSchemas: map[string]*tfprotov5.Schema{}, - Diagnostics: []*tfprotov5.Diagnostic{ - { - Severity: tfprotov5.DiagnosticSeverityError, - Summary: "Duplicate Data Source Type Defined", - Detail: "The test_data_source data source type name was returned for multiple data sources. " + - "Data source type names must be unique. " + - "This is always an issue with the provider and should be reported to the provider developers.", - }, - }, - EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, - Functions: map[string]*tfprotov5.Function{}, - ListResourceSchemas: map[string]*tfprotov5.Schema{}, - Provider: &tfprotov5.Schema{ - Block: &tfprotov5.SchemaBlock{}, - }, - ResourceSchemas: map[string]*tfprotov5.Schema{}, - ServerCapabilities: &tfprotov5.ServerCapabilities{ - GetProviderSchemaOptional: true, - MoveResourceState: true, - PlanDestroy: true, - }, - }, - }, - "datasourceschemas-empty-type-name": { - server: &Server{ - FrameworkServer: fwserver.Server{ - Provider: &testprovider.Provider{ - DataSourcesMethod: func(_ context.Context) []func() datasource.DataSource { - return []func() datasource.DataSource{ - func() datasource.DataSource { - return &testprovider.DataSource{ - MetadataMethod: func(_ context.Context, _ datasource.MetadataRequest, resp *datasource.MetadataResponse) { - resp.TypeName = "" - }, - } + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, + DataSourceSchemas: map[string]*tfprotov5.Schema{ + "test_data_source1": { + Block: &tfprotov5.SchemaBlock{ + Attributes: []*tfprotov5.SchemaAttribute{ + { + Name: "test1", + Required: true, + Type: tftypes.String, }, - } + }, }, }, - }, - }, - request: &tfprotov5.GetProviderSchemaRequest{}, - expectedResponse: &tfprotov5.GetProviderSchemaResponse{ - DataSourceSchemas: map[string]*tfprotov5.Schema{}, - Diagnostics: []*tfprotov5.Diagnostic{ - { - Severity: tfprotov5.DiagnosticSeverityError, - Summary: "Data Source Type Name Missing", - Detail: "The *testprovider.DataSource DataSource returned an empty string from the Metadata method. " + - "This is always an issue with the provider and should be reported to the provider developers.", + "test_data_source2": { + Block: &tfprotov5.SchemaBlock{ + Attributes: []*tfprotov5.SchemaAttribute{ + { + Name: "test2", + Required: true, + Type: tftypes.String, + }, + }, + }, }, }, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, @@ -278,6 +259,7 @@ func TestServerGetProviderSchema(t *testing.T) { }, request: &tfprotov5.GetProviderSchemaRequest{}, expectedResponse: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{ "test_ephemeral_resource1": { @@ -316,118 +298,6 @@ func TestServerGetProviderSchema(t *testing.T) { }, }, }, - "ephemeralschemas-duplicate-type-name": { - server: &Server{ - FrameworkServer: fwserver.Server{ - Provider: &testprovider.Provider{ - EphemeralResourcesMethod: func(_ context.Context) []func() ephemeral.EphemeralResource { - return []func() ephemeral.EphemeralResource{ - func() ephemeral.EphemeralResource { - return &testprovider.EphemeralResource{ - SchemaMethod: func(_ context.Context, _ ephemeral.SchemaRequest, resp *ephemeral.SchemaResponse) { - resp.Schema = ephemeralschema.Schema{ - Attributes: map[string]ephemeralschema.Attribute{ - "test1": ephemeralschema.StringAttribute{ - Required: true, - }, - }, - } - }, - MetadataMethod: func(_ context.Context, _ ephemeral.MetadataRequest, resp *ephemeral.MetadataResponse) { - resp.TypeName = "test_ephemeral_resource" - }, - } - }, - func() ephemeral.EphemeralResource { - return &testprovider.EphemeralResource{ - SchemaMethod: func(_ context.Context, _ ephemeral.SchemaRequest, resp *ephemeral.SchemaResponse) { - resp.Schema = ephemeralschema.Schema{ - Attributes: map[string]ephemeralschema.Attribute{ - "test2": ephemeralschema.StringAttribute{ - Required: true, - }, - }, - } - }, - MetadataMethod: func(_ context.Context, _ ephemeral.MetadataRequest, resp *ephemeral.MetadataResponse) { - resp.TypeName = "test_ephemeral_resource" - }, - } - }, - } - }, - }, - }, - }, - request: &tfprotov5.GetProviderSchemaRequest{}, - expectedResponse: &tfprotov5.GetProviderSchemaResponse{ - DataSourceSchemas: map[string]*tfprotov5.Schema{}, - Diagnostics: []*tfprotov5.Diagnostic{ - { - Severity: tfprotov5.DiagnosticSeverityError, - Summary: "Duplicate Ephemeral Resource Type Defined", - Detail: "The test_ephemeral_resource ephemeral resource type name was returned for multiple ephemeral resources. " + - "Ephemeral resource type names must be unique. " + - "This is always an issue with the provider and should be reported to the provider developers.", - }, - }, - Functions: map[string]*tfprotov5.Function{}, - EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, - ListResourceSchemas: map[string]*tfprotov5.Schema{}, - Provider: &tfprotov5.Schema{ - Block: &tfprotov5.SchemaBlock{}, - }, - ResourceSchemas: map[string]*tfprotov5.Schema{}, - ServerCapabilities: &tfprotov5.ServerCapabilities{ - GetProviderSchemaOptional: true, - MoveResourceState: true, - PlanDestroy: true, - }, - }, - }, - "ephemeralschemas-empty-type-name": { - server: &Server{ - FrameworkServer: fwserver.Server{ - Provider: &testprovider.Provider{ - EphemeralResourcesMethod: func(_ context.Context) []func() ephemeral.EphemeralResource { - return []func() ephemeral.EphemeralResource{ - func() ephemeral.EphemeralResource { - return &testprovider.EphemeralResource{ - MetadataMethod: func(_ context.Context, _ ephemeral.MetadataRequest, resp *ephemeral.MetadataResponse) { - resp.TypeName = "" - }, - } - }, - } - }, - }, - }, - }, - request: &tfprotov5.GetProviderSchemaRequest{}, - expectedResponse: &tfprotov5.GetProviderSchemaResponse{ - DataSourceSchemas: map[string]*tfprotov5.Schema{}, - Diagnostics: []*tfprotov5.Diagnostic{ - { - Severity: tfprotov5.DiagnosticSeverityError, - Summary: "Ephemeral Resource Type Name Missing", - Detail: "The *testprovider.EphemeralResource EphemeralResource returned an empty string from the Metadata method. " + - "This is always an issue with the provider and should be reported to the provider developers.", - }, - }, - EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, - Functions: map[string]*tfprotov5.Function{}, - ListResourceSchemas: map[string]*tfprotov5.Schema{}, - Provider: &tfprotov5.Schema{ - Block: &tfprotov5.SchemaBlock{}, - }, - ResourceSchemas: map[string]*tfprotov5.Schema{}, - ServerCapabilities: &tfprotov5.ServerCapabilities{ - GetProviderSchemaOptional: true, - MoveResourceState: true, - PlanDestroy: true, - }, - }, - }, "functions": { server: &Server{ FrameworkServer: fwserver.Server{ @@ -465,6 +335,7 @@ func TestServerGetProviderSchema(t *testing.T) { }, request: &tfprotov5.GetProviderSchemaRequest{}, expectedResponse: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{ @@ -493,110 +364,6 @@ func TestServerGetProviderSchema(t *testing.T) { }, }, }, - "functions-duplicate-type-name": { - server: &Server{ - FrameworkServer: fwserver.Server{ - Provider: &testprovider.ProviderWithFunctions{ - FunctionsMethod: func(_ context.Context) []func() function.Function { - return []func() function.Function{ - func() function.Function { - return &testprovider.Function{ - DefinitionMethod: func(_ context.Context, _ function.DefinitionRequest, resp *function.DefinitionResponse) { - resp.Definition = function.Definition{ - Return: function.StringReturn{}, - } - }, - MetadataMethod: func(_ context.Context, _ function.MetadataRequest, resp *function.MetadataResponse) { - resp.Name = "testfunction" // intentionally duplicate - }, - } - }, - func() function.Function { - return &testprovider.Function{ - DefinitionMethod: func(_ context.Context, _ function.DefinitionRequest, resp *function.DefinitionResponse) { - resp.Definition = function.Definition{ - Return: function.StringReturn{}, - } - }, - MetadataMethod: func(_ context.Context, _ function.MetadataRequest, resp *function.MetadataResponse) { - resp.Name = "testfunction" // intentionally duplicate - }, - } - }, - } - }, - }, - }, - }, - request: &tfprotov5.GetProviderSchemaRequest{}, - expectedResponse: &tfprotov5.GetProviderSchemaResponse{ - DataSourceSchemas: map[string]*tfprotov5.Schema{}, - Diagnostics: []*tfprotov5.Diagnostic{ - { - Severity: tfprotov5.DiagnosticSeverityError, - Summary: "Duplicate Function Name Defined", - Detail: "The testfunction function name was returned for multiple functions. " + - "Function names must be unique. " + - "This is always an issue with the provider and should be reported to the provider developers.", - }, - }, - EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, - Functions: map[string]*tfprotov5.Function{}, - ListResourceSchemas: map[string]*tfprotov5.Schema{}, - Provider: &tfprotov5.Schema{ - Block: &tfprotov5.SchemaBlock{}, - }, - ResourceSchemas: map[string]*tfprotov5.Schema{}, - ServerCapabilities: &tfprotov5.ServerCapabilities{ - GetProviderSchemaOptional: true, - MoveResourceState: true, - PlanDestroy: true, - }, - }, - }, - "functions-empty-name": { - server: &Server{ - FrameworkServer: fwserver.Server{ - Provider: &testprovider.ProviderWithFunctions{ - FunctionsMethod: func(_ context.Context) []func() function.Function { - return []func() function.Function{ - func() function.Function { - return &testprovider.Function{ - MetadataMethod: func(_ context.Context, _ function.MetadataRequest, resp *function.MetadataResponse) { - resp.Name = "" // intentionally empty - }, - } - }, - } - }, - }, - }, - }, - request: &tfprotov5.GetProviderSchemaRequest{}, - expectedResponse: &tfprotov5.GetProviderSchemaResponse{ - DataSourceSchemas: map[string]*tfprotov5.Schema{}, - Diagnostics: []*tfprotov5.Diagnostic{ - { - Severity: tfprotov5.DiagnosticSeverityError, - Summary: "Function Name Missing", - Detail: "The *testprovider.Function Function returned an empty string from the Metadata method. " + - "This is always an issue with the provider and should be reported to the provider developers.", - }, - }, - EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, - Functions: map[string]*tfprotov5.Function{}, - ListResourceSchemas: map[string]*tfprotov5.Schema{}, - Provider: &tfprotov5.Schema{ - Block: &tfprotov5.SchemaBlock{}, - }, - ResourceSchemas: map[string]*tfprotov5.Schema{}, - ServerCapabilities: &tfprotov5.ServerCapabilities{ - GetProviderSchemaOptional: true, - MoveResourceState: true, - PlanDestroy: true, - }, - }, - }, "listschemas": { server: &Server{ FrameworkServer: fwserver.Server{ @@ -615,149 +382,7 @@ func TestServerGetProviderSchema(t *testing.T) { } }, MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "test_list_resource1" - }, - } - }, - func() list.ListResource { - return &testprovider.ListResource{ - ListResourceConfigSchemaMethod: func(_ context.Context, _ list.ListResourceSchemaRequest, resp *list.ListResourceSchemaResponse) { - resp.Schema = listschema.Schema{ - Attributes: map[string]listschema.Attribute{ - "test2": listschema.StringAttribute{ - Required: true, - }, - }, - } - }, - MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "test_list_resource2" - }, - } - }, - } - }, - ResourcesMethod: func(_ context.Context) []func() resource.Resource { - return []func() resource.Resource{ - func() resource.Resource { - return &testprovider.Resource{ - SchemaMethod: func(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = resourceschema.Schema{ - Attributes: map[string]resourceschema.Attribute{ - "test1": resourceschema.StringAttribute{ - Required: true, - }, - }, - } - }, - MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "test_list_resource1" - }, - } - }, - func() resource.Resource { - return &testprovider.Resource{ - SchemaMethod: func(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = resourceschema.Schema{ - Attributes: map[string]resourceschema.Attribute{ - "test2": resourceschema.StringAttribute{ - Required: true, - }, - }, - } - }, - MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "test_list_resource2" - }, - } - }, - } - }, - }, - }, - }, - request: &tfprotov5.GetProviderSchemaRequest{}, - expectedResponse: &tfprotov5.GetProviderSchemaResponse{ - DataSourceSchemas: map[string]*tfprotov5.Schema{}, - EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, - Functions: map[string]*tfprotov5.Function{}, - ListResourceSchemas: map[string]*tfprotov5.Schema{ - "test_list_resource1": { - Block: &tfprotov5.SchemaBlock{ - Attributes: []*tfprotov5.SchemaAttribute{ - { - Name: "test1", - Required: true, - Type: tftypes.String, - }, - }, - }, - }, - "test_list_resource2": { - Block: &tfprotov5.SchemaBlock{ - Attributes: []*tfprotov5.SchemaAttribute{ - { - Name: "test2", - Required: true, - Type: tftypes.String, - }, - }, - }, - }, - }, - Provider: &tfprotov5.Schema{ - Block: &tfprotov5.SchemaBlock{}, - }, - ResourceSchemas: map[string]*tfprotov5.Schema{ - "test_list_resource1": { - Block: &tfprotov5.SchemaBlock{ - Attributes: []*tfprotov5.SchemaAttribute{ - { - Name: "test1", - Required: true, - Type: tftypes.String, - }, - }, - }, - }, - "test_list_resource2": { - Block: &tfprotov5.SchemaBlock{ - Attributes: []*tfprotov5.SchemaAttribute{ - { - Name: "test2", - Required: true, - Type: tftypes.String, - }, - }, - }, - }, - }, - ServerCapabilities: &tfprotov5.ServerCapabilities{ - GetProviderSchemaOptional: true, - MoveResourceState: true, - PlanDestroy: true, - }, - }, - }, - "listschemas-duplicate-type-name": { - server: &Server{ - FrameworkServer: fwserver.Server{ - Provider: &testprovider.Provider{ - ListResourcesMethod: func(_ context.Context) []func() list.ListResource { - return []func() list.ListResource{ - func() list.ListResource { - return &testprovider.ListResource{ - ListResourceConfigSchemaMethod: func(_ context.Context, _ list.ListResourceSchemaRequest, resp *list.ListResourceSchemaResponse) { - resp.Schema = listschema.Schema{ - Attributes: map[string]listschema.Attribute{ - "test1": listschema.StringAttribute{ - Required: true, - }, - }, - } - }, - MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "test_list_resource" + resp.TypeName = "test_list_resource1" }, } }, @@ -773,7 +398,7 @@ func TestServerGetProviderSchema(t *testing.T) { } }, MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "test_list_resource" + resp.TypeName = "test_list_resource2" }, } }, @@ -793,83 +418,23 @@ func TestServerGetProviderSchema(t *testing.T) { } }, MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "test_list_resource" - }, - } - }, - } - }, - }, - }, - }, - request: &tfprotov5.GetProviderSchemaRequest{}, - expectedResponse: &tfprotov5.GetProviderSchemaResponse{ - DataSourceSchemas: map[string]*tfprotov5.Schema{}, - Diagnostics: []*tfprotov5.Diagnostic{ - { - Severity: tfprotov5.DiagnosticSeverityError, - Summary: "Duplicate ListResource Type Defined", - Detail: "The test_list_resource ListResource type name was returned for multiple list resources. " + - "ListResource type names must be unique. " + - "This is always an issue with the provider and should be reported to the provider developers.", - }, - }, - Functions: map[string]*tfprotov5.Function{}, - EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, - ListResourceSchemas: map[string]*tfprotov5.Schema{}, - Provider: &tfprotov5.Schema{ - Block: &tfprotov5.SchemaBlock{}, - }, - ResourceSchemas: map[string]*tfprotov5.Schema{ - "test_list_resource": { - Block: &tfprotov5.SchemaBlock{ - Attributes: []*tfprotov5.SchemaAttribute{ - { - Name: "test1", - Required: true, - Type: tftypes.String, - }, - }, - }, - }, - }, - ServerCapabilities: &tfprotov5.ServerCapabilities{ - GetProviderSchemaOptional: true, - MoveResourceState: true, - PlanDestroy: true, - }, - }, - }, - "listschemas-empty-type-name": { - server: &Server{ - FrameworkServer: fwserver.Server{ - Provider: &testprovider.Provider{ - ListResourcesMethod: func(_ context.Context) []func() list.ListResource { - return []func() list.ListResource{ - func() list.ListResource { - return &testprovider.ListResource{ - MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "" + resp.TypeName = "test_list_resource1" }, } }, - } - }, - ResourcesMethod: func(_ context.Context) []func() resource.Resource { - return []func() resource.Resource{ func() resource.Resource { return &testprovider.Resource{ SchemaMethod: func(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = resourceschema.Schema{ Attributes: map[string]resourceschema.Attribute{ - "test1": resourceschema.StringAttribute{ + "test2": resourceschema.StringAttribute{ Required: true, }, }, } }, MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "test_list_resource" + resp.TypeName = "test_list_resource2" }, } }, @@ -880,23 +445,39 @@ func TestServerGetProviderSchema(t *testing.T) { }, request: &tfprotov5.GetProviderSchemaRequest{}, expectedResponse: &tfprotov5.GetProviderSchemaResponse{ - DataSourceSchemas: map[string]*tfprotov5.Schema{}, - Diagnostics: []*tfprotov5.Diagnostic{ - { - Severity: tfprotov5.DiagnosticSeverityError, - Summary: "ListResource Type Name Missing", - Detail: "The *testprovider.ListResource ListResource returned an empty string from the Metadata method. " + - "This is always an issue with the provider and should be reported to the provider developers.", - }, - }, + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, + DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, - ListResourceSchemas: map[string]*tfprotov5.Schema{}, + ListResourceSchemas: map[string]*tfprotov5.Schema{ + "test_list_resource1": { + Block: &tfprotov5.SchemaBlock{ + Attributes: []*tfprotov5.SchemaAttribute{ + { + Name: "test1", + Required: true, + Type: tftypes.String, + }, + }, + }, + }, + "test_list_resource2": { + Block: &tfprotov5.SchemaBlock{ + Attributes: []*tfprotov5.SchemaAttribute{ + { + Name: "test2", + Required: true, + Type: tftypes.String, + }, + }, + }, + }, + }, Provider: &tfprotov5.Schema{ Block: &tfprotov5.SchemaBlock{}, }, ResourceSchemas: map[string]*tfprotov5.Schema{ - "test_list_resource": { + "test_list_resource1": { Block: &tfprotov5.SchemaBlock{ Attributes: []*tfprotov5.SchemaAttribute{ { @@ -907,59 +488,18 @@ func TestServerGetProviderSchema(t *testing.T) { }, }, }, - }, - ServerCapabilities: &tfprotov5.ServerCapabilities{ - GetProviderSchemaOptional: true, - MoveResourceState: true, - PlanDestroy: true, - }, - }, - }, - "listschemas-missing-resource-definition": { - server: &Server{ - FrameworkServer: fwserver.Server{ - Provider: &testprovider.Provider{ - ListResourcesMethod: func(_ context.Context) []func() list.ListResource { - return []func() list.ListResource{ - func() list.ListResource { - return &testprovider.ListResource{ - ListResourceConfigSchemaMethod: func(_ context.Context, _ list.ListResourceSchemaRequest, resp *list.ListResourceSchemaResponse) { - resp.Schema = listschema.Schema{ - Attributes: map[string]listschema.Attribute{ - "test1": listschema.StringAttribute{ - Required: true, - }, - }, - } - }, - MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "test_list_resource" - }, - } + "test_list_resource2": { + Block: &tfprotov5.SchemaBlock{ + Attributes: []*tfprotov5.SchemaAttribute{ + { + Name: "test2", + Required: true, + Type: tftypes.String, }, - } + }, }, }, }, - }, - request: &tfprotov5.GetProviderSchemaRequest{}, - expectedResponse: &tfprotov5.GetProviderSchemaResponse{ - DataSourceSchemas: map[string]*tfprotov5.Schema{}, - Diagnostics: []*tfprotov5.Diagnostic{ - { - Severity: tfprotov5.DiagnosticSeverityError, - Summary: "ListResource Type Defined without a Matching Managed Resource Type", - Detail: "The test_list_resource ListResource type name was returned, but no matching managed Resource type was defined. " + - "This is always an issue with the provider and should be reported to the provider developers.", - }, - }, - EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, - Functions: map[string]*tfprotov5.Function{}, - ListResourceSchemas: map[string]*tfprotov5.Schema{}, - Provider: &tfprotov5.Schema{ - Block: &tfprotov5.SchemaBlock{}, - }, - ResourceSchemas: map[string]*tfprotov5.Schema{}, ServerCapabilities: &tfprotov5.ServerCapabilities{ GetProviderSchemaOptional: true, MoveResourceState: true, @@ -985,6 +525,7 @@ func TestServerGetProviderSchema(t *testing.T) { }, request: &tfprotov5.GetProviderSchemaRequest{}, expectedResponse: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -1027,6 +568,7 @@ func TestServerGetProviderSchema(t *testing.T) { }, request: &tfprotov5.GetProviderSchemaRequest{}, expectedResponse: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -1098,6 +640,7 @@ func TestServerGetProviderSchema(t *testing.T) { }, request: &tfprotov5.GetProviderSchemaRequest{}, expectedResponse: &tfprotov5.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov5.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov5.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, Functions: map[string]*tfprotov5.Function{}, @@ -1136,118 +679,6 @@ func TestServerGetProviderSchema(t *testing.T) { }, }, }, - "resourceschemas-duplicate-type-name": { - server: &Server{ - FrameworkServer: fwserver.Server{ - Provider: &testprovider.Provider{ - ResourcesMethod: func(_ context.Context) []func() resource.Resource { - return []func() resource.Resource{ - func() resource.Resource { - return &testprovider.Resource{ - SchemaMethod: func(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = resourceschema.Schema{ - Attributes: map[string]resourceschema.Attribute{ - "test1": resourceschema.StringAttribute{ - Required: true, - }, - }, - } - }, - MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "test_resource" - }, - } - }, - func() resource.Resource { - return &testprovider.Resource{ - SchemaMethod: func(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = resourceschema.Schema{ - Attributes: map[string]resourceschema.Attribute{ - "test2": resourceschema.StringAttribute{ - Required: true, - }, - }, - } - }, - MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "test_resource" - }, - } - }, - } - }, - }, - }, - }, - request: &tfprotov5.GetProviderSchemaRequest{}, - expectedResponse: &tfprotov5.GetProviderSchemaResponse{ - DataSourceSchemas: map[string]*tfprotov5.Schema{}, - Diagnostics: []*tfprotov5.Diagnostic{ - { - Severity: tfprotov5.DiagnosticSeverityError, - Summary: "Duplicate Resource Type Defined", - Detail: "The test_resource resource type name was returned for multiple resources. " + - "Resource type names must be unique. " + - "This is always an issue with the provider and should be reported to the provider developers.", - }, - }, - EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, - Functions: map[string]*tfprotov5.Function{}, - ListResourceSchemas: map[string]*tfprotov5.Schema{}, - Provider: &tfprotov5.Schema{ - Block: &tfprotov5.SchemaBlock{}, - }, - ResourceSchemas: map[string]*tfprotov5.Schema{}, - ServerCapabilities: &tfprotov5.ServerCapabilities{ - GetProviderSchemaOptional: true, - MoveResourceState: true, - PlanDestroy: true, - }, - }, - }, - "resourceschemas-empty-type-name": { - server: &Server{ - FrameworkServer: fwserver.Server{ - Provider: &testprovider.Provider{ - ResourcesMethod: func(_ context.Context) []func() resource.Resource { - return []func() resource.Resource{ - func() resource.Resource { - return &testprovider.Resource{ - MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "" - }, - } - }, - } - }, - }, - }, - }, - request: &tfprotov5.GetProviderSchemaRequest{}, - expectedResponse: &tfprotov5.GetProviderSchemaResponse{ - DataSourceSchemas: map[string]*tfprotov5.Schema{}, - Diagnostics: []*tfprotov5.Diagnostic{ - { - Severity: tfprotov5.DiagnosticSeverityError, - Summary: "Resource Type Name Missing", - Detail: "The *testprovider.Resource Resource returned an empty string from the Metadata method. " + - "This is always an issue with the provider and should be reported to the provider developers.", - }, - }, - EphemeralResourceSchemas: map[string]*tfprotov5.Schema{}, - Functions: map[string]*tfprotov5.Function{}, - ListResourceSchemas: map[string]*tfprotov5.Schema{}, - Provider: &tfprotov5.Schema{ - Block: &tfprotov5.SchemaBlock{}, - }, - ResourceSchemas: map[string]*tfprotov5.Schema{}, - ServerCapabilities: &tfprotov5.ServerCapabilities{ - GetProviderSchemaOptional: true, - MoveResourceState: true, - PlanDestroy: true, - }, - }, - }, } for name, testCase := range testCases { @@ -1434,6 +865,36 @@ func TestServerGetProviderSchema_logging(t *testing.T) { "@message": string("Called provider defined ListResources"), "@module": string("sdk.framework"), }, + { + "@level": string("trace"), + "@message": string("Checking ActionFuncs lock"), + "@module": string("sdk.framework"), + }, + { + "@level": string("trace"), + "@message": string("Checking ProviderTypeName lock"), + "@module": string("sdk.framework"), + }, + { + "@level": string("trace"), + "@message": string("Calling provider defined Provider Metadata"), + "@module": string("sdk.framework"), + }, + { + "@level": string("trace"), + "@message": string("Called provider defined Provider Metadata"), + "@module": string("sdk.framework"), + }, + { + "@level": string("trace"), + "@message": string("Calling provider defined Actions"), + "@module": string("sdk.framework"), + }, + { + "@level": string("trace"), + "@message": string("Called provider defined Actions"), + "@module": string("sdk.framework"), + }, } if diff := cmp.Diff(entries, expectedEntries); diff != "" { diff --git a/internal/proto6server/server_getmetadata_test.go b/internal/proto6server/server_getmetadata_test.go index 36c0f39d0..090091f27 100644 --- a/internal/proto6server/server_getmetadata_test.go +++ b/internal/proto6server/server_getmetadata_test.go @@ -9,6 +9,7 @@ import ( "testing" "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/action" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/ephemeral" "github.com/hashicorp/terraform-plugin-framework/function" @@ -28,23 +29,23 @@ func TestServerGetMetadata(t *testing.T) { expectedError error expectedResponse *tfprotov6.GetMetadataResponse }{ - "datasources": { + "actions": { server: &Server{ FrameworkServer: fwserver.Server{ Provider: &testprovider.Provider{ - DataSourcesMethod: func(_ context.Context) []func() datasource.DataSource { - return []func() datasource.DataSource{ - func() datasource.DataSource { - return &testprovider.DataSource{ - MetadataMethod: func(_ context.Context, _ datasource.MetadataRequest, resp *datasource.MetadataResponse) { - resp.TypeName = "test_data_source1" + ActionsMethod: func(_ context.Context) []func() action.Action { + return []func() action.Action{ + func() action.Action { + return &testprovider.Action{ + MetadataMethod: func(_ context.Context, _ action.MetadataRequest, resp *action.MetadataResponse) { + resp.TypeName = "test_action1" }, } }, - func() datasource.DataSource { - return &testprovider.DataSource{ - MetadataMethod: func(_ context.Context, _ datasource.MetadataRequest, resp *datasource.MetadataResponse) { - resp.TypeName = "test_data_source2" + func() action.Action { + return &testprovider.Action{ + MetadataMethod: func(_ context.Context, _ action.MetadataRequest, resp *action.MetadataResponse) { + resp.TypeName = "test_action2" }, } }, @@ -55,14 +56,15 @@ func TestServerGetMetadata(t *testing.T) { }, request: &tfprotov6.GetMetadataRequest{}, expectedResponse: &tfprotov6.GetMetadataResponse{ - DataSources: []tfprotov6.DataSourceMetadata{ + Actions: []tfprotov6.ActionMetadata{ { - TypeName: "test_data_source1", + TypeName: "test_action1", }, { - TypeName: "test_data_source2", + TypeName: "test_action2", }, }, + DataSources: []tfprotov6.DataSourceMetadata{}, EphemeralResources: []tfprotov6.EphemeralResourceMetadata{}, Functions: []tfprotov6.FunctionMetadata{}, ListResources: []tfprotov6.ListResourceMetadata{}, @@ -74,7 +76,7 @@ func TestServerGetMetadata(t *testing.T) { }, }, }, - "datasources-duplicate-type-name": { + "datasources": { server: &Server{ FrameworkServer: fwserver.Server{ Provider: &testprovider.Provider{ @@ -83,14 +85,14 @@ func TestServerGetMetadata(t *testing.T) { func() datasource.DataSource { return &testprovider.DataSource{ MetadataMethod: func(_ context.Context, _ datasource.MetadataRequest, resp *datasource.MetadataResponse) { - resp.TypeName = "test_data_source" + resp.TypeName = "test_data_source1" }, } }, func() datasource.DataSource { return &testprovider.DataSource{ MetadataMethod: func(_ context.Context, _ datasource.MetadataRequest, resp *datasource.MetadataResponse) { - resp.TypeName = "test_data_source" + resp.TypeName = "test_data_source2" }, } }, @@ -101,54 +103,13 @@ func TestServerGetMetadata(t *testing.T) { }, request: &tfprotov6.GetMetadataRequest{}, expectedResponse: &tfprotov6.GetMetadataResponse{ - DataSources: []tfprotov6.DataSourceMetadata{}, - Diagnostics: []*tfprotov6.Diagnostic{ + Actions: []tfprotov6.ActionMetadata{}, + DataSources: []tfprotov6.DataSourceMetadata{ { - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Duplicate Data Source Type Defined", - Detail: "The test_data_source data source type name was returned for multiple data sources. " + - "Data source type names must be unique. " + - "This is always an issue with the provider and should be reported to the provider developers.", - }, - }, - EphemeralResources: []tfprotov6.EphemeralResourceMetadata{}, - Functions: []tfprotov6.FunctionMetadata{}, - ListResources: []tfprotov6.ListResourceMetadata{}, - Resources: []tfprotov6.ResourceMetadata{}, - ServerCapabilities: &tfprotov6.ServerCapabilities{ - GetProviderSchemaOptional: true, - MoveResourceState: true, - PlanDestroy: true, - }, - }, - }, - "datasources-empty-type-name": { - server: &Server{ - FrameworkServer: fwserver.Server{ - Provider: &testprovider.Provider{ - DataSourcesMethod: func(_ context.Context) []func() datasource.DataSource { - return []func() datasource.DataSource{ - func() datasource.DataSource { - return &testprovider.DataSource{ - MetadataMethod: func(_ context.Context, _ datasource.MetadataRequest, resp *datasource.MetadataResponse) { - resp.TypeName = "" - }, - } - }, - } - }, + TypeName: "test_data_source1", }, - }, - }, - request: &tfprotov6.GetMetadataRequest{}, - expectedResponse: &tfprotov6.GetMetadataResponse{ - DataSources: []tfprotov6.DataSourceMetadata{}, - Diagnostics: []*tfprotov6.Diagnostic{ { - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Data Source Type Name Missing", - Detail: "The *testprovider.DataSource DataSource returned an empty string from the Metadata method. " + - "This is always an issue with the provider and should be reported to the provider developers.", + TypeName: "test_data_source2", }, }, EphemeralResources: []tfprotov6.EphemeralResourceMetadata{}, @@ -189,6 +150,7 @@ func TestServerGetMetadata(t *testing.T) { }, request: &tfprotov6.GetMetadataRequest{}, expectedResponse: &tfprotov6.GetMetadataResponse{ + Actions: []tfprotov6.ActionMetadata{}, DataSources: []tfprotov6.DataSourceMetadata{}, EphemeralResources: []tfprotov6.EphemeralResourceMetadata{ { @@ -208,94 +170,6 @@ func TestServerGetMetadata(t *testing.T) { }, }, }, - "ephemeralresources-duplicate-type-name": { - server: &Server{ - FrameworkServer: fwserver.Server{ - Provider: &testprovider.Provider{ - EphemeralResourcesMethod: func(_ context.Context) []func() ephemeral.EphemeralResource { - return []func() ephemeral.EphemeralResource{ - func() ephemeral.EphemeralResource { - return &testprovider.EphemeralResource{ - MetadataMethod: func(_ context.Context, _ ephemeral.MetadataRequest, resp *ephemeral.MetadataResponse) { - resp.TypeName = "test_ephemeral_resource" - }, - } - }, - func() ephemeral.EphemeralResource { - return &testprovider.EphemeralResource{ - MetadataMethod: func(_ context.Context, _ ephemeral.MetadataRequest, resp *ephemeral.MetadataResponse) { - resp.TypeName = "test_ephemeral_resource" - }, - } - }, - } - }, - }, - }, - }, - request: &tfprotov6.GetMetadataRequest{}, - expectedResponse: &tfprotov6.GetMetadataResponse{ - DataSources: []tfprotov6.DataSourceMetadata{}, - Diagnostics: []*tfprotov6.Diagnostic{ - { - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Duplicate Ephemeral Resource Type Defined", - Detail: "The test_ephemeral_resource ephemeral resource type name was returned for multiple ephemeral resources. " + - "Ephemeral resource type names must be unique. " + - "This is always an issue with the provider and should be reported to the provider developers.", - }, - }, - EphemeralResources: []tfprotov6.EphemeralResourceMetadata{}, - Functions: []tfprotov6.FunctionMetadata{}, - ListResources: []tfprotov6.ListResourceMetadata{}, - Resources: []tfprotov6.ResourceMetadata{}, - ServerCapabilities: &tfprotov6.ServerCapabilities{ - GetProviderSchemaOptional: true, - MoveResourceState: true, - PlanDestroy: true, - }, - }, - }, - "ephemeralresources-empty-type-name": { - server: &Server{ - FrameworkServer: fwserver.Server{ - Provider: &testprovider.Provider{ - EphemeralResourcesMethod: func(_ context.Context) []func() ephemeral.EphemeralResource { - return []func() ephemeral.EphemeralResource{ - func() ephemeral.EphemeralResource { - return &testprovider.EphemeralResource{ - MetadataMethod: func(_ context.Context, _ ephemeral.MetadataRequest, resp *ephemeral.MetadataResponse) { - resp.TypeName = "" - }, - } - }, - } - }, - }, - }, - }, - request: &tfprotov6.GetMetadataRequest{}, - expectedResponse: &tfprotov6.GetMetadataResponse{ - DataSources: []tfprotov6.DataSourceMetadata{}, - Diagnostics: []*tfprotov6.Diagnostic{ - { - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Ephemeral Resource Type Name Missing", - Detail: "The *testprovider.EphemeralResource EphemeralResource returned an empty string from the Metadata method. " + - "This is always an issue with the provider and should be reported to the provider developers.", - }, - }, - EphemeralResources: []tfprotov6.EphemeralResourceMetadata{}, - Functions: []tfprotov6.FunctionMetadata{}, - ListResources: []tfprotov6.ListResourceMetadata{}, - Resources: []tfprotov6.ResourceMetadata{}, - ServerCapabilities: &tfprotov6.ServerCapabilities{ - GetProviderSchemaOptional: true, - MoveResourceState: true, - PlanDestroy: true, - }, - }, - }, "functions": { server: &Server{ FrameworkServer: fwserver.Server{ @@ -323,6 +197,7 @@ func TestServerGetMetadata(t *testing.T) { }, request: &tfprotov6.GetMetadataRequest{}, expectedResponse: &tfprotov6.GetMetadataResponse{ + Actions: []tfprotov6.ActionMetadata{}, DataSources: []tfprotov6.DataSourceMetadata{}, EphemeralResources: []tfprotov6.EphemeralResourceMetadata{}, Functions: []tfprotov6.FunctionMetadata{ @@ -342,94 +217,6 @@ func TestServerGetMetadata(t *testing.T) { }, }, }, - "functions-duplicate-type-name": { - server: &Server{ - FrameworkServer: fwserver.Server{ - Provider: &testprovider.ProviderWithFunctions{ - FunctionsMethod: func(_ context.Context) []func() function.Function { - return []func() function.Function{ - func() function.Function { - return &testprovider.Function{ - MetadataMethod: func(_ context.Context, _ function.MetadataRequest, resp *function.MetadataResponse) { - resp.Name = "testfunction" // intentionally duplicate - }, - } - }, - func() function.Function { - return &testprovider.Function{ - MetadataMethod: func(_ context.Context, _ function.MetadataRequest, resp *function.MetadataResponse) { - resp.Name = "testfunction" // intentionally duplicate - }, - } - }, - } - }, - }, - }, - }, - request: &tfprotov6.GetMetadataRequest{}, - expectedResponse: &tfprotov6.GetMetadataResponse{ - DataSources: []tfprotov6.DataSourceMetadata{}, - Diagnostics: []*tfprotov6.Diagnostic{ - { - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Duplicate Function Name Defined", - Detail: "The testfunction function name was returned for multiple functions. " + - "Function names must be unique. " + - "This is always an issue with the provider and should be reported to the provider developers.", - }, - }, - EphemeralResources: []tfprotov6.EphemeralResourceMetadata{}, - Functions: []tfprotov6.FunctionMetadata{}, - ListResources: []tfprotov6.ListResourceMetadata{}, - Resources: []tfprotov6.ResourceMetadata{}, - ServerCapabilities: &tfprotov6.ServerCapabilities{ - GetProviderSchemaOptional: true, - MoveResourceState: true, - PlanDestroy: true, - }, - }, - }, - "functions-empty-type-name": { - server: &Server{ - FrameworkServer: fwserver.Server{ - Provider: &testprovider.ProviderWithFunctions{ - FunctionsMethod: func(_ context.Context) []func() function.Function { - return []func() function.Function{ - func() function.Function { - return &testprovider.Function{ - MetadataMethod: func(_ context.Context, _ function.MetadataRequest, resp *function.MetadataResponse) { - resp.Name = "" // intentionally empty - }, - } - }, - } - }, - }, - }, - }, - request: &tfprotov6.GetMetadataRequest{}, - expectedResponse: &tfprotov6.GetMetadataResponse{ - DataSources: []tfprotov6.DataSourceMetadata{}, - Diagnostics: []*tfprotov6.Diagnostic{ - { - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Function Name Missing", - Detail: "The *testprovider.Function Function returned an empty string from the Metadata method. " + - "This is always an issue with the provider and should be reported to the provider developers.", - }, - }, - EphemeralResources: []tfprotov6.EphemeralResourceMetadata{}, - Functions: []tfprotov6.FunctionMetadata{}, - ListResources: []tfprotov6.ListResourceMetadata{}, - Resources: []tfprotov6.ResourceMetadata{}, - ServerCapabilities: &tfprotov6.ServerCapabilities{ - GetProviderSchemaOptional: true, - MoveResourceState: true, - PlanDestroy: true, - }, - }, - }, "listresources": { server: &Server{ FrameworkServer: fwserver.Server{ @@ -475,6 +262,7 @@ func TestServerGetMetadata(t *testing.T) { }, request: &tfprotov6.GetMetadataRequest{}, expectedResponse: &tfprotov6.GetMetadataResponse{ + Actions: []tfprotov6.ActionMetadata{}, DataSources: []tfprotov6.DataSourceMetadata{}, EphemeralResources: []tfprotov6.EphemeralResourceMetadata{}, Functions: []tfprotov6.FunctionMetadata{}, @@ -501,156 +289,6 @@ func TestServerGetMetadata(t *testing.T) { }, }, }, - "listresources-duplicate-type-name": { - server: &Server{ - FrameworkServer: fwserver.Server{ - Provider: &testprovider.Provider{ - ListResourcesMethod: func(_ context.Context) []func() list.ListResource { - return []func() list.ListResource{ - func() list.ListResource { - return &testprovider.ListResource{ - MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "test_list_resource" - }, - } - }, - func() list.ListResource { - return &testprovider.ListResource{ - MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "test_list_resource" - }, - } - }, - } - }, - ResourcesMethod: func(_ context.Context) []func() resource.Resource { - return []func() resource.Resource{ - func() resource.Resource { - return &testprovider.Resource{ - MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "test_list_resource" - }, - } - }, - } - }, - }, - }, - }, - request: &tfprotov6.GetMetadataRequest{}, - expectedResponse: &tfprotov6.GetMetadataResponse{ - DataSources: []tfprotov6.DataSourceMetadata{}, - Diagnostics: []*tfprotov6.Diagnostic{ - { - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Duplicate ListResource Type Defined", - Detail: "The test_list_resource ListResource type name was returned for multiple list resources. " + - "ListResource type names must be unique. " + - "This is always an issue with the provider and should be reported to the provider developers.", - }, - }, - EphemeralResources: []tfprotov6.EphemeralResourceMetadata{}, - Functions: []tfprotov6.FunctionMetadata{}, - ListResources: []tfprotov6.ListResourceMetadata{}, - Resources: []tfprotov6.ResourceMetadata{}, - ServerCapabilities: &tfprotov6.ServerCapabilities{ - GetProviderSchemaOptional: true, - MoveResourceState: true, - PlanDestroy: true, - }, - }, - }, - "listresources-empty-type-name": { - server: &Server{ - FrameworkServer: fwserver.Server{ - Provider: &testprovider.Provider{ - ListResourcesMethod: func(_ context.Context) []func() list.ListResource { - return []func() list.ListResource{ - func() list.ListResource { - return &testprovider.ListResource{ - MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "" - }, - } - }, - } - }, - ResourcesMethod: func(_ context.Context) []func() resource.Resource { - return []func() resource.Resource{ - func() resource.Resource { - return &testprovider.Resource{ - MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "test_list_resource" - }, - } - }, - } - }, - }, - }, - }, - request: &tfprotov6.GetMetadataRequest{}, - expectedResponse: &tfprotov6.GetMetadataResponse{ - DataSources: []tfprotov6.DataSourceMetadata{}, - Diagnostics: []*tfprotov6.Diagnostic{ - { - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "ListResource Type Name Missing", - Detail: "The *testprovider.ListResource ListResource returned an empty string from the Metadata method. " + - "This is always an issue with the provider and should be reported to the provider developers.", - }, - }, - EphemeralResources: []tfprotov6.EphemeralResourceMetadata{}, - Functions: []tfprotov6.FunctionMetadata{}, - ListResources: []tfprotov6.ListResourceMetadata{}, - Resources: []tfprotov6.ResourceMetadata{}, - ServerCapabilities: &tfprotov6.ServerCapabilities{ - GetProviderSchemaOptional: true, - MoveResourceState: true, - PlanDestroy: true, - }, - }, - }, - "listresources-missing-resource-definition": { - server: &Server{ - FrameworkServer: fwserver.Server{ - Provider: &testprovider.Provider{ - ListResourcesMethod: func(_ context.Context) []func() list.ListResource { - return []func() list.ListResource{ - func() list.ListResource { - return &testprovider.ListResource{ - MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "test_list_resource" - }, - } - }, - } - }, - }, - }, - }, - request: &tfprotov6.GetMetadataRequest{}, - expectedResponse: &tfprotov6.GetMetadataResponse{ - DataSources: []tfprotov6.DataSourceMetadata{}, - Diagnostics: []*tfprotov6.Diagnostic{ - { - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "ListResource Type Defined without a Matching Managed Resource Type", - Detail: "The test_list_resource ListResource type name was returned, but no matching managed Resource type was defined. " + - "This is always an issue with the provider and should be reported to the provider developers.", - }, - }, - EphemeralResources: []tfprotov6.EphemeralResourceMetadata{}, - Functions: []tfprotov6.FunctionMetadata{}, - ListResources: []tfprotov6.ListResourceMetadata{}, - Resources: []tfprotov6.ResourceMetadata{}, - ServerCapabilities: &tfprotov6.ServerCapabilities{ - GetProviderSchemaOptional: true, - MoveResourceState: true, - PlanDestroy: true, - }, - }, - }, "resources": { server: &Server{ FrameworkServer: fwserver.Server{ @@ -678,6 +316,7 @@ func TestServerGetMetadata(t *testing.T) { }, request: &tfprotov6.GetMetadataRequest{}, expectedResponse: &tfprotov6.GetMetadataResponse{ + Actions: []tfprotov6.ActionMetadata{}, DataSources: []tfprotov6.DataSourceMetadata{}, EphemeralResources: []tfprotov6.EphemeralResourceMetadata{}, Functions: []tfprotov6.FunctionMetadata{}, @@ -697,94 +336,6 @@ func TestServerGetMetadata(t *testing.T) { }, }, }, - "resources-duplicate-type-name": { - server: &Server{ - FrameworkServer: fwserver.Server{ - Provider: &testprovider.Provider{ - ResourcesMethod: func(_ context.Context) []func() resource.Resource { - return []func() resource.Resource{ - func() resource.Resource { - return &testprovider.Resource{ - MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "test_resource" - }, - } - }, - func() resource.Resource { - return &testprovider.Resource{ - MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "test_resource" - }, - } - }, - } - }, - }, - }, - }, - request: &tfprotov6.GetMetadataRequest{}, - expectedResponse: &tfprotov6.GetMetadataResponse{ - DataSources: []tfprotov6.DataSourceMetadata{}, - Diagnostics: []*tfprotov6.Diagnostic{ - { - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Duplicate Resource Type Defined", - Detail: "The test_resource resource type name was returned for multiple resources. " + - "Resource type names must be unique. " + - "This is always an issue with the provider and should be reported to the provider developers.", - }, - }, - EphemeralResources: []tfprotov6.EphemeralResourceMetadata{}, - Functions: []tfprotov6.FunctionMetadata{}, - ListResources: []tfprotov6.ListResourceMetadata{}, - Resources: []tfprotov6.ResourceMetadata{}, - ServerCapabilities: &tfprotov6.ServerCapabilities{ - GetProviderSchemaOptional: true, - MoveResourceState: true, - PlanDestroy: true, - }, - }, - }, - "resources-empty-type-name": { - server: &Server{ - FrameworkServer: fwserver.Server{ - Provider: &testprovider.Provider{ - ResourcesMethod: func(_ context.Context) []func() resource.Resource { - return []func() resource.Resource{ - func() resource.Resource { - return &testprovider.Resource{ - MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "" - }, - } - }, - } - }, - }, - }, - }, - request: &tfprotov6.GetMetadataRequest{}, - expectedResponse: &tfprotov6.GetMetadataResponse{ - DataSources: []tfprotov6.DataSourceMetadata{}, - Diagnostics: []*tfprotov6.Diagnostic{ - { - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Resource Type Name Missing", - Detail: "The *testprovider.Resource Resource returned an empty string from the Metadata method. " + - "This is always an issue with the provider and should be reported to the provider developers.", - }, - }, - EphemeralResources: []tfprotov6.EphemeralResourceMetadata{}, - Functions: []tfprotov6.FunctionMetadata{}, - ListResources: []tfprotov6.ListResourceMetadata{}, - Resources: []tfprotov6.ResourceMetadata{}, - ServerCapabilities: &tfprotov6.ServerCapabilities{ - GetProviderSchemaOptional: true, - MoveResourceState: true, - PlanDestroy: true, - }, - }, - }, } for name, testCase := range testCases { diff --git a/internal/proto6server/server_getproviderschema_test.go b/internal/proto6server/server_getproviderschema_test.go index a1010f555..e93e75b70 100644 --- a/internal/proto6server/server_getproviderschema_test.go +++ b/internal/proto6server/server_getproviderschema_test.go @@ -9,6 +9,8 @@ import ( "testing" "github.com/google/go-cmp/cmp" + "github.com/hashicorp/terraform-plugin-framework/action" + actionschema "github.com/hashicorp/terraform-plugin-framework/action/schema" "github.com/hashicorp/terraform-plugin-framework/datasource" datasourceschema "github.com/hashicorp/terraform-plugin-framework/datasource/schema" "github.com/hashicorp/terraform-plugin-framework/ephemeral" @@ -38,41 +40,41 @@ func TestServerGetProviderSchema(t *testing.T) { expectedError error expectedResponse *tfprotov6.GetProviderSchemaResponse }{ - "datasourceschemas": { + "actionschemas": { server: &Server{ FrameworkServer: fwserver.Server{ Provider: &testprovider.Provider{ - DataSourcesMethod: func(_ context.Context) []func() datasource.DataSource { - return []func() datasource.DataSource{ - func() datasource.DataSource { - return &testprovider.DataSource{ - SchemaMethod: func(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { - resp.Schema = datasourceschema.Schema{ - Attributes: map[string]datasourceschema.Attribute{ - "test1": datasourceschema.StringAttribute{ + ActionsMethod: func(_ context.Context) []func() action.Action { + return []func() action.Action{ + func() action.Action { + return &testprovider.Action{ + SchemaMethod: func(_ context.Context, _ action.SchemaRequest, resp *action.SchemaResponse) { + resp.Schema = actionschema.UnlinkedSchema{ + Attributes: map[string]actionschema.Attribute{ + "test1": actionschema.StringAttribute{ Required: true, }, }, } }, - MetadataMethod: func(_ context.Context, _ datasource.MetadataRequest, resp *datasource.MetadataResponse) { - resp.TypeName = "test_data_source1" + MetadataMethod: func(_ context.Context, _ action.MetadataRequest, resp *action.MetadataResponse) { + resp.TypeName = "test_action1" }, } }, - func() datasource.DataSource { - return &testprovider.DataSource{ - SchemaMethod: func(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { - resp.Schema = datasourceschema.Schema{ - Attributes: map[string]datasourceschema.Attribute{ - "test2": datasourceschema.StringAttribute{ + func() action.Action { + return &testprovider.Action{ + SchemaMethod: func(_ context.Context, _ action.SchemaRequest, resp *action.SchemaResponse) { + resp.Schema = actionschema.UnlinkedSchema{ + Attributes: map[string]actionschema.Attribute{ + "test2": actionschema.StringAttribute{ Required: true, }, }, } }, - MetadataMethod: func(_ context.Context, _ datasource.MetadataRequest, resp *datasource.MetadataResponse) { - resp.TypeName = "test_data_source2" + MetadataMethod: func(_ context.Context, _ action.MetadataRequest, resp *action.MetadataResponse) { + resp.TypeName = "test_action2" }, } }, @@ -83,30 +85,37 @@ func TestServerGetProviderSchema(t *testing.T) { }, request: &tfprotov6.GetProviderSchemaRequest{}, expectedResponse: &tfprotov6.GetProviderSchemaResponse{ - DataSourceSchemas: map[string]*tfprotov6.Schema{ - "test_data_source1": { - Block: &tfprotov6.SchemaBlock{ - Attributes: []*tfprotov6.SchemaAttribute{ - { - Name: "test1", - Required: true, - Type: tftypes.String, + ActionSchemas: map[string]*tfprotov6.ActionSchema{ + "test_action1": { + Type: tfprotov6.UnlinkedActionSchemaType{}, + Schema: &tfprotov6.Schema{ + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "test1", + Required: true, + Type: tftypes.String, + }, }, }, }, }, - "test_data_source2": { - Block: &tfprotov6.SchemaBlock{ - Attributes: []*tfprotov6.SchemaAttribute{ - { - Name: "test2", - Required: true, - Type: tftypes.String, + "test_action2": { + Type: tfprotov6.UnlinkedActionSchemaType{}, + Schema: &tfprotov6.Schema{ + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "test2", + Required: true, + Type: tftypes.String, + }, }, }, }, }, }, + DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, ListResourceSchemas: map[string]*tfprotov6.Schema{}, @@ -121,7 +130,7 @@ func TestServerGetProviderSchema(t *testing.T) { }, }, }, - "datasourceschemas-duplicate-type-name": { + "datasourceschemas": { server: &Server{ FrameworkServer: fwserver.Server{ Provider: &testprovider.Provider{ @@ -139,7 +148,7 @@ func TestServerGetProviderSchema(t *testing.T) { } }, MetadataMethod: func(_ context.Context, _ datasource.MetadataRequest, resp *datasource.MetadataResponse) { - resp.TypeName = "test_data_source" + resp.TypeName = "test_data_source1" }, } }, @@ -155,7 +164,7 @@ func TestServerGetProviderSchema(t *testing.T) { } }, MetadataMethod: func(_ context.Context, _ datasource.MetadataRequest, resp *datasource.MetadataResponse) { - resp.TypeName = "test_data_source" + resp.TypeName = "test_data_source2" }, } }, @@ -166,57 +175,29 @@ func TestServerGetProviderSchema(t *testing.T) { }, request: &tfprotov6.GetProviderSchemaRequest{}, expectedResponse: &tfprotov6.GetProviderSchemaResponse{ - DataSourceSchemas: map[string]*tfprotov6.Schema{}, - Diagnostics: []*tfprotov6.Diagnostic{ - { - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Duplicate Data Source Type Defined", - Detail: "The test_data_source data source type name was returned for multiple data sources. " + - "Data source type names must be unique. " + - "This is always an issue with the provider and should be reported to the provider developers.", - }, - }, - EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, - Functions: map[string]*tfprotov6.Function{}, - ListResourceSchemas: map[string]*tfprotov6.Schema{}, - Provider: &tfprotov6.Schema{ - Block: &tfprotov6.SchemaBlock{}, - }, - ResourceSchemas: map[string]*tfprotov6.Schema{}, - ServerCapabilities: &tfprotov6.ServerCapabilities{ - GetProviderSchemaOptional: true, - MoveResourceState: true, - PlanDestroy: true, - }, - }, - }, - "datasourceschemas-empty-type-name": { - server: &Server{ - FrameworkServer: fwserver.Server{ - Provider: &testprovider.Provider{ - DataSourcesMethod: func(_ context.Context) []func() datasource.DataSource { - return []func() datasource.DataSource{ - func() datasource.DataSource { - return &testprovider.DataSource{ - MetadataMethod: func(_ context.Context, _ datasource.MetadataRequest, resp *datasource.MetadataResponse) { - resp.TypeName = "" - }, - } + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, + DataSourceSchemas: map[string]*tfprotov6.Schema{ + "test_data_source1": { + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "test1", + Required: true, + Type: tftypes.String, }, - } + }, }, }, - }, - }, - request: &tfprotov6.GetProviderSchemaRequest{}, - expectedResponse: &tfprotov6.GetProviderSchemaResponse{ - DataSourceSchemas: map[string]*tfprotov6.Schema{}, - Diagnostics: []*tfprotov6.Diagnostic{ - { - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Data Source Type Name Missing", - Detail: "The *testprovider.DataSource DataSource returned an empty string from the Metadata method. " + - "This is always an issue with the provider and should be reported to the provider developers.", + "test_data_source2": { + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "test2", + Required: true, + Type: tftypes.String, + }, + }, + }, }, }, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, @@ -278,6 +259,7 @@ func TestServerGetProviderSchema(t *testing.T) { }, request: &tfprotov6.GetProviderSchemaRequest{}, expectedResponse: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{ "test_ephemeral_resource1": { @@ -316,118 +298,6 @@ func TestServerGetProviderSchema(t *testing.T) { }, }, }, - "ephemeralschemas-duplicate-type-name": { - server: &Server{ - FrameworkServer: fwserver.Server{ - Provider: &testprovider.Provider{ - EphemeralResourcesMethod: func(_ context.Context) []func() ephemeral.EphemeralResource { - return []func() ephemeral.EphemeralResource{ - func() ephemeral.EphemeralResource { - return &testprovider.EphemeralResource{ - SchemaMethod: func(_ context.Context, _ ephemeral.SchemaRequest, resp *ephemeral.SchemaResponse) { - resp.Schema = ephemeralschema.Schema{ - Attributes: map[string]ephemeralschema.Attribute{ - "test1": ephemeralschema.StringAttribute{ - Required: true, - }, - }, - } - }, - MetadataMethod: func(_ context.Context, _ ephemeral.MetadataRequest, resp *ephemeral.MetadataResponse) { - resp.TypeName = "test_ephemeral_resource" - }, - } - }, - func() ephemeral.EphemeralResource { - return &testprovider.EphemeralResource{ - SchemaMethod: func(_ context.Context, _ ephemeral.SchemaRequest, resp *ephemeral.SchemaResponse) { - resp.Schema = ephemeralschema.Schema{ - Attributes: map[string]ephemeralschema.Attribute{ - "test2": ephemeralschema.StringAttribute{ - Required: true, - }, - }, - } - }, - MetadataMethod: func(_ context.Context, _ ephemeral.MetadataRequest, resp *ephemeral.MetadataResponse) { - resp.TypeName = "test_ephemeral_resource" - }, - } - }, - } - }, - }, - }, - }, - request: &tfprotov6.GetProviderSchemaRequest{}, - expectedResponse: &tfprotov6.GetProviderSchemaResponse{ - DataSourceSchemas: map[string]*tfprotov6.Schema{}, - Diagnostics: []*tfprotov6.Diagnostic{ - { - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Duplicate Ephemeral Resource Type Defined", - Detail: "The test_ephemeral_resource ephemeral resource type name was returned for multiple ephemeral resources. " + - "Ephemeral resource type names must be unique. " + - "This is always an issue with the provider and should be reported to the provider developers.", - }, - }, - EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, - Functions: map[string]*tfprotov6.Function{}, - ListResourceSchemas: map[string]*tfprotov6.Schema{}, - Provider: &tfprotov6.Schema{ - Block: &tfprotov6.SchemaBlock{}, - }, - ResourceSchemas: map[string]*tfprotov6.Schema{}, - ServerCapabilities: &tfprotov6.ServerCapabilities{ - GetProviderSchemaOptional: true, - MoveResourceState: true, - PlanDestroy: true, - }, - }, - }, - "ephemeralschemas-empty-type-name": { - server: &Server{ - FrameworkServer: fwserver.Server{ - Provider: &testprovider.Provider{ - EphemeralResourcesMethod: func(_ context.Context) []func() ephemeral.EphemeralResource { - return []func() ephemeral.EphemeralResource{ - func() ephemeral.EphemeralResource { - return &testprovider.EphemeralResource{ - MetadataMethod: func(_ context.Context, _ ephemeral.MetadataRequest, resp *ephemeral.MetadataResponse) { - resp.TypeName = "" - }, - } - }, - } - }, - }, - }, - }, - request: &tfprotov6.GetProviderSchemaRequest{}, - expectedResponse: &tfprotov6.GetProviderSchemaResponse{ - DataSourceSchemas: map[string]*tfprotov6.Schema{}, - Diagnostics: []*tfprotov6.Diagnostic{ - { - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Ephemeral Resource Type Name Missing", - Detail: "The *testprovider.EphemeralResource EphemeralResource returned an empty string from the Metadata method. " + - "This is always an issue with the provider and should be reported to the provider developers.", - }, - }, - EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, - Functions: map[string]*tfprotov6.Function{}, - ListResourceSchemas: map[string]*tfprotov6.Schema{}, - Provider: &tfprotov6.Schema{ - Block: &tfprotov6.SchemaBlock{}, - }, - ResourceSchemas: map[string]*tfprotov6.Schema{}, - ServerCapabilities: &tfprotov6.ServerCapabilities{ - GetProviderSchemaOptional: true, - MoveResourceState: true, - PlanDestroy: true, - }, - }, - }, "functions": { server: &Server{ FrameworkServer: fwserver.Server{ @@ -465,6 +335,7 @@ func TestServerGetProviderSchema(t *testing.T) { }, request: &tfprotov6.GetProviderSchemaRequest{}, expectedResponse: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{ @@ -493,110 +364,6 @@ func TestServerGetProviderSchema(t *testing.T) { }, }, }, - "functions-duplicate-type-name": { - server: &Server{ - FrameworkServer: fwserver.Server{ - Provider: &testprovider.ProviderWithFunctions{ - FunctionsMethod: func(_ context.Context) []func() function.Function { - return []func() function.Function{ - func() function.Function { - return &testprovider.Function{ - DefinitionMethod: func(_ context.Context, _ function.DefinitionRequest, resp *function.DefinitionResponse) { - resp.Definition = function.Definition{ - Return: function.StringReturn{}, - } - }, - MetadataMethod: func(_ context.Context, _ function.MetadataRequest, resp *function.MetadataResponse) { - resp.Name = "testfunction" // intentionally duplicate - }, - } - }, - func() function.Function { - return &testprovider.Function{ - DefinitionMethod: func(_ context.Context, _ function.DefinitionRequest, resp *function.DefinitionResponse) { - resp.Definition = function.Definition{ - Return: function.StringReturn{}, - } - }, - MetadataMethod: func(_ context.Context, _ function.MetadataRequest, resp *function.MetadataResponse) { - resp.Name = "testfunction" // intentionally duplicate - }, - } - }, - } - }, - }, - }, - }, - request: &tfprotov6.GetProviderSchemaRequest{}, - expectedResponse: &tfprotov6.GetProviderSchemaResponse{ - DataSourceSchemas: map[string]*tfprotov6.Schema{}, - Diagnostics: []*tfprotov6.Diagnostic{ - { - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Duplicate Function Name Defined", - Detail: "The testfunction function name was returned for multiple functions. " + - "Function names must be unique. " + - "This is always an issue with the provider and should be reported to the provider developers.", - }, - }, - EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, - Functions: map[string]*tfprotov6.Function{}, - ListResourceSchemas: map[string]*tfprotov6.Schema{}, - Provider: &tfprotov6.Schema{ - Block: &tfprotov6.SchemaBlock{}, - }, - ResourceSchemas: map[string]*tfprotov6.Schema{}, - ServerCapabilities: &tfprotov6.ServerCapabilities{ - GetProviderSchemaOptional: true, - MoveResourceState: true, - PlanDestroy: true, - }, - }, - }, - "functions-empty-name": { - server: &Server{ - FrameworkServer: fwserver.Server{ - Provider: &testprovider.ProviderWithFunctions{ - FunctionsMethod: func(_ context.Context) []func() function.Function { - return []func() function.Function{ - func() function.Function { - return &testprovider.Function{ - MetadataMethod: func(_ context.Context, _ function.MetadataRequest, resp *function.MetadataResponse) { - resp.Name = "" // intentionally empty - }, - } - }, - } - }, - }, - }, - }, - request: &tfprotov6.GetProviderSchemaRequest{}, - expectedResponse: &tfprotov6.GetProviderSchemaResponse{ - DataSourceSchemas: map[string]*tfprotov6.Schema{}, - Diagnostics: []*tfprotov6.Diagnostic{ - { - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Function Name Missing", - Detail: "The *testprovider.Function Function returned an empty string from the Metadata method. " + - "This is always an issue with the provider and should be reported to the provider developers.", - }, - }, - EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, - Functions: map[string]*tfprotov6.Function{}, - ListResourceSchemas: map[string]*tfprotov6.Schema{}, - Provider: &tfprotov6.Schema{ - Block: &tfprotov6.SchemaBlock{}, - }, - ResourceSchemas: map[string]*tfprotov6.Schema{}, - ServerCapabilities: &tfprotov6.ServerCapabilities{ - GetProviderSchemaOptional: true, - MoveResourceState: true, - PlanDestroy: true, - }, - }, - }, "listschemas": { server: &Server{ FrameworkServer: fwserver.Server{ @@ -615,149 +382,7 @@ func TestServerGetProviderSchema(t *testing.T) { } }, MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "test_list_resource1" - }, - } - }, - func() list.ListResource { - return &testprovider.ListResource{ - ListResourceConfigSchemaMethod: func(_ context.Context, _ list.ListResourceSchemaRequest, resp *list.ListResourceSchemaResponse) { - resp.Schema = listschema.Schema{ - Attributes: map[string]listschema.Attribute{ - "test2": listschema.StringAttribute{ - Required: true, - }, - }, - } - }, - MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "test_list_resource2" - }, - } - }, - } - }, - ResourcesMethod: func(_ context.Context) []func() resource.Resource { - return []func() resource.Resource{ - func() resource.Resource { - return &testprovider.Resource{ - SchemaMethod: func(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = resourceschema.Schema{ - Attributes: map[string]resourceschema.Attribute{ - "test1": resourceschema.StringAttribute{ - Required: true, - }, - }, - } - }, - MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "test_list_resource1" - }, - } - }, - func() resource.Resource { - return &testprovider.Resource{ - SchemaMethod: func(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = resourceschema.Schema{ - Attributes: map[string]resourceschema.Attribute{ - "test2": resourceschema.StringAttribute{ - Required: true, - }, - }, - } - }, - MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "test_list_resource2" - }, - } - }, - } - }, - }, - }, - }, - request: &tfprotov6.GetProviderSchemaRequest{}, - expectedResponse: &tfprotov6.GetProviderSchemaResponse{ - DataSourceSchemas: map[string]*tfprotov6.Schema{}, - EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, - Functions: map[string]*tfprotov6.Function{}, - ListResourceSchemas: map[string]*tfprotov6.Schema{ - "test_list_resource1": { - Block: &tfprotov6.SchemaBlock{ - Attributes: []*tfprotov6.SchemaAttribute{ - { - Name: "test1", - Required: true, - Type: tftypes.String, - }, - }, - }, - }, - "test_list_resource2": { - Block: &tfprotov6.SchemaBlock{ - Attributes: []*tfprotov6.SchemaAttribute{ - { - Name: "test2", - Required: true, - Type: tftypes.String, - }, - }, - }, - }, - }, - Provider: &tfprotov6.Schema{ - Block: &tfprotov6.SchemaBlock{}, - }, - ResourceSchemas: map[string]*tfprotov6.Schema{ - "test_list_resource1": { - Block: &tfprotov6.SchemaBlock{ - Attributes: []*tfprotov6.SchemaAttribute{ - { - Name: "test1", - Required: true, - Type: tftypes.String, - }, - }, - }, - }, - "test_list_resource2": { - Block: &tfprotov6.SchemaBlock{ - Attributes: []*tfprotov6.SchemaAttribute{ - { - Name: "test2", - Required: true, - Type: tftypes.String, - }, - }, - }, - }, - }, - ServerCapabilities: &tfprotov6.ServerCapabilities{ - GetProviderSchemaOptional: true, - MoveResourceState: true, - PlanDestroy: true, - }, - }, - }, - "listschemas-duplicate-type-name": { - server: &Server{ - FrameworkServer: fwserver.Server{ - Provider: &testprovider.Provider{ - ListResourcesMethod: func(_ context.Context) []func() list.ListResource { - return []func() list.ListResource{ - func() list.ListResource { - return &testprovider.ListResource{ - ListResourceConfigSchemaMethod: func(_ context.Context, _ list.ListResourceSchemaRequest, resp *list.ListResourceSchemaResponse) { - resp.Schema = listschema.Schema{ - Attributes: map[string]listschema.Attribute{ - "test1": listschema.StringAttribute{ - Required: true, - }, - }, - } - }, - MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "test_list_resource" + resp.TypeName = "test_list_resource1" }, } }, @@ -773,7 +398,7 @@ func TestServerGetProviderSchema(t *testing.T) { } }, MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "test_list_resource" + resp.TypeName = "test_list_resource2" }, } }, @@ -793,83 +418,23 @@ func TestServerGetProviderSchema(t *testing.T) { } }, MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "test_list_resource" - }, - } - }, - } - }, - }, - }, - }, - request: &tfprotov6.GetProviderSchemaRequest{}, - expectedResponse: &tfprotov6.GetProviderSchemaResponse{ - DataSourceSchemas: map[string]*tfprotov6.Schema{}, - Diagnostics: []*tfprotov6.Diagnostic{ - { - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Duplicate ListResource Type Defined", - Detail: "The test_list_resource ListResource type name was returned for multiple list resources. " + - "ListResource type names must be unique. " + - "This is always an issue with the provider and should be reported to the provider developers.", - }, - }, - EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, - Functions: map[string]*tfprotov6.Function{}, - ListResourceSchemas: map[string]*tfprotov6.Schema{}, - Provider: &tfprotov6.Schema{ - Block: &tfprotov6.SchemaBlock{}, - }, - ResourceSchemas: map[string]*tfprotov6.Schema{ - "test_list_resource": { - Block: &tfprotov6.SchemaBlock{ - Attributes: []*tfprotov6.SchemaAttribute{ - { - Name: "test1", - Required: true, - Type: tftypes.String, - }, - }, - }, - }, - }, - ServerCapabilities: &tfprotov6.ServerCapabilities{ - GetProviderSchemaOptional: true, - MoveResourceState: true, - PlanDestroy: true, - }, - }, - }, - "listschemas-empty-type-name": { - server: &Server{ - FrameworkServer: fwserver.Server{ - Provider: &testprovider.Provider{ - ListResourcesMethod: func(_ context.Context) []func() list.ListResource { - return []func() list.ListResource{ - func() list.ListResource { - return &testprovider.ListResource{ - MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "" + resp.TypeName = "test_list_resource1" }, } }, - } - }, - ResourcesMethod: func(_ context.Context) []func() resource.Resource { - return []func() resource.Resource{ func() resource.Resource { return &testprovider.Resource{ SchemaMethod: func(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = resourceschema.Schema{ Attributes: map[string]resourceschema.Attribute{ - "test1": resourceschema.StringAttribute{ + "test2": resourceschema.StringAttribute{ Required: true, }, }, } }, MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "test_list_resource" + resp.TypeName = "test_list_resource2" }, } }, @@ -880,23 +445,39 @@ func TestServerGetProviderSchema(t *testing.T) { }, request: &tfprotov6.GetProviderSchemaRequest{}, expectedResponse: &tfprotov6.GetProviderSchemaResponse{ - DataSourceSchemas: map[string]*tfprotov6.Schema{}, - Diagnostics: []*tfprotov6.Diagnostic{ - { - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "ListResource Type Name Missing", - Detail: "The *testprovider.ListResource ListResource returned an empty string from the Metadata method. " + - "This is always an issue with the provider and should be reported to the provider developers.", - }, - }, + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, + DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, - ListResourceSchemas: map[string]*tfprotov6.Schema{}, + ListResourceSchemas: map[string]*tfprotov6.Schema{ + "test_list_resource1": { + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "test1", + Required: true, + Type: tftypes.String, + }, + }, + }, + }, + "test_list_resource2": { + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "test2", + Required: true, + Type: tftypes.String, + }, + }, + }, + }, + }, Provider: &tfprotov6.Schema{ Block: &tfprotov6.SchemaBlock{}, }, ResourceSchemas: map[string]*tfprotov6.Schema{ - "test_list_resource": { + "test_list_resource1": { Block: &tfprotov6.SchemaBlock{ Attributes: []*tfprotov6.SchemaAttribute{ { @@ -907,59 +488,18 @@ func TestServerGetProviderSchema(t *testing.T) { }, }, }, - }, - ServerCapabilities: &tfprotov6.ServerCapabilities{ - GetProviderSchemaOptional: true, - MoveResourceState: true, - PlanDestroy: true, - }, - }, - }, - "listschemas-missing-resource-definition": { - server: &Server{ - FrameworkServer: fwserver.Server{ - Provider: &testprovider.Provider{ - ListResourcesMethod: func(_ context.Context) []func() list.ListResource { - return []func() list.ListResource{ - func() list.ListResource { - return &testprovider.ListResource{ - ListResourceConfigSchemaMethod: func(_ context.Context, _ list.ListResourceSchemaRequest, resp *list.ListResourceSchemaResponse) { - resp.Schema = listschema.Schema{ - Attributes: map[string]listschema.Attribute{ - "test1": listschema.StringAttribute{ - Required: true, - }, - }, - } - }, - MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "test_list_resource" - }, - } + "test_list_resource2": { + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "test2", + Required: true, + Type: tftypes.String, }, - } + }, }, }, }, - }, - request: &tfprotov6.GetProviderSchemaRequest{}, - expectedResponse: &tfprotov6.GetProviderSchemaResponse{ - DataSourceSchemas: map[string]*tfprotov6.Schema{}, - Diagnostics: []*tfprotov6.Diagnostic{ - { - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "ListResource Type Defined without a Matching Managed Resource Type", - Detail: "The test_list_resource ListResource type name was returned, but no matching managed Resource type was defined. " + - "This is always an issue with the provider and should be reported to the provider developers.", - }, - }, - EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, - Functions: map[string]*tfprotov6.Function{}, - ListResourceSchemas: map[string]*tfprotov6.Schema{}, - Provider: &tfprotov6.Schema{ - Block: &tfprotov6.SchemaBlock{}, - }, - ResourceSchemas: map[string]*tfprotov6.Schema{}, ServerCapabilities: &tfprotov6.ServerCapabilities{ GetProviderSchemaOptional: true, MoveResourceState: true, @@ -985,6 +525,7 @@ func TestServerGetProviderSchema(t *testing.T) { }, request: &tfprotov6.GetProviderSchemaRequest{}, expectedResponse: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -1027,6 +568,7 @@ func TestServerGetProviderSchema(t *testing.T) { }, request: &tfprotov6.GetProviderSchemaRequest{}, expectedResponse: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -1098,6 +640,7 @@ func TestServerGetProviderSchema(t *testing.T) { }, request: &tfprotov6.GetProviderSchemaRequest{}, expectedResponse: &tfprotov6.GetProviderSchemaResponse{ + ActionSchemas: map[string]*tfprotov6.ActionSchema{}, DataSourceSchemas: map[string]*tfprotov6.Schema{}, EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, Functions: map[string]*tfprotov6.Function{}, @@ -1136,118 +679,6 @@ func TestServerGetProviderSchema(t *testing.T) { }, }, }, - "resourceschemas-duplicate-type-name": { - server: &Server{ - FrameworkServer: fwserver.Server{ - Provider: &testprovider.Provider{ - ResourcesMethod: func(_ context.Context) []func() resource.Resource { - return []func() resource.Resource{ - func() resource.Resource { - return &testprovider.Resource{ - SchemaMethod: func(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = resourceschema.Schema{ - Attributes: map[string]resourceschema.Attribute{ - "test1": resourceschema.StringAttribute{ - Required: true, - }, - }, - } - }, - MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "test_resource" - }, - } - }, - func() resource.Resource { - return &testprovider.Resource{ - SchemaMethod: func(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { - resp.Schema = resourceschema.Schema{ - Attributes: map[string]resourceschema.Attribute{ - "test2": resourceschema.StringAttribute{ - Required: true, - }, - }, - } - }, - MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "test_resource" - }, - } - }, - } - }, - }, - }, - }, - request: &tfprotov6.GetProviderSchemaRequest{}, - expectedResponse: &tfprotov6.GetProviderSchemaResponse{ - DataSourceSchemas: map[string]*tfprotov6.Schema{}, - Diagnostics: []*tfprotov6.Diagnostic{ - { - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Duplicate Resource Type Defined", - Detail: "The test_resource resource type name was returned for multiple resources. " + - "Resource type names must be unique. " + - "This is always an issue with the provider and should be reported to the provider developers.", - }, - }, - EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, - Functions: map[string]*tfprotov6.Function{}, - ListResourceSchemas: map[string]*tfprotov6.Schema{}, - Provider: &tfprotov6.Schema{ - Block: &tfprotov6.SchemaBlock{}, - }, - ResourceSchemas: map[string]*tfprotov6.Schema{}, - ServerCapabilities: &tfprotov6.ServerCapabilities{ - GetProviderSchemaOptional: true, - MoveResourceState: true, - PlanDestroy: true, - }, - }, - }, - "resourceschemas-empty-type-name": { - server: &Server{ - FrameworkServer: fwserver.Server{ - Provider: &testprovider.Provider{ - ResourcesMethod: func(_ context.Context) []func() resource.Resource { - return []func() resource.Resource{ - func() resource.Resource { - return &testprovider.Resource{ - MetadataMethod: func(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { - resp.TypeName = "" - }, - } - }, - } - }, - }, - }, - }, - request: &tfprotov6.GetProviderSchemaRequest{}, - expectedResponse: &tfprotov6.GetProviderSchemaResponse{ - DataSourceSchemas: map[string]*tfprotov6.Schema{}, - Diagnostics: []*tfprotov6.Diagnostic{ - { - Severity: tfprotov6.DiagnosticSeverityError, - Summary: "Resource Type Name Missing", - Detail: "The *testprovider.Resource Resource returned an empty string from the Metadata method. " + - "This is always an issue with the provider and should be reported to the provider developers.", - }, - }, - EphemeralResourceSchemas: map[string]*tfprotov6.Schema{}, - Functions: map[string]*tfprotov6.Function{}, - ListResourceSchemas: map[string]*tfprotov6.Schema{}, - Provider: &tfprotov6.Schema{ - Block: &tfprotov6.SchemaBlock{}, - }, - ResourceSchemas: map[string]*tfprotov6.Schema{}, - ServerCapabilities: &tfprotov6.ServerCapabilities{ - GetProviderSchemaOptional: true, - MoveResourceState: true, - PlanDestroy: true, - }, - }, - }, } for name, testCase := range testCases { @@ -1434,6 +865,36 @@ func TestServerGetProviderSchema_logging(t *testing.T) { "@message": string("Called provider defined ListResources"), "@module": string("sdk.framework"), }, + { + "@level": string("trace"), + "@message": string("Checking ActionFuncs lock"), + "@module": string("sdk.framework"), + }, + { + "@level": string("trace"), + "@message": string("Checking ProviderTypeName lock"), + "@module": string("sdk.framework"), + }, + { + "@level": string("trace"), + "@message": string("Calling provider defined Provider Metadata"), + "@module": string("sdk.framework"), + }, + { + "@level": string("trace"), + "@message": string("Called provider defined Provider Metadata"), + "@module": string("sdk.framework"), + }, + { + "@level": string("trace"), + "@message": string("Calling provider defined Actions"), + "@module": string("sdk.framework"), + }, + { + "@level": string("trace"), + "@message": string("Called provider defined Actions"), + "@module": string("sdk.framework"), + }, } if diff := cmp.Diff(entries, expectedEntries); diff != "" { From 3a1048c60d18617eee24a25689d6268e523a87c8 Mon Sep 17 00:00:00 2001 From: Austin Valle Date: Fri, 11 Jul 2025 09:36:44 -0400 Subject: [PATCH 8/8] fix double import --- internal/toproto5/action_schema_test.go | 3 +-- internal/toproto6/action_schema_test.go | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/internal/toproto5/action_schema_test.go b/internal/toproto5/action_schema_test.go index 717d19162..39b7e7c7f 100644 --- a/internal/toproto5/action_schema_test.go +++ b/internal/toproto5/action_schema_test.go @@ -8,7 +8,6 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/hashicorp/terraform-plugin-framework/action/schema" actionschema "github.com/hashicorp/terraform-plugin-framework/action/schema" "github.com/hashicorp/terraform-plugin-framework/internal/toproto5" "github.com/hashicorp/terraform-plugin-go/tfprotov5" @@ -40,7 +39,7 @@ func TestActionSchema(t *testing.T) { }, }, Blocks: map[string]actionschema.Block{ - "single_block": schema.SingleNestedBlock{ + "single_block": actionschema.SingleNestedBlock{ Attributes: map[string]actionschema.Attribute{ "bool": actionschema.BoolAttribute{ Required: true, diff --git a/internal/toproto6/action_schema_test.go b/internal/toproto6/action_schema_test.go index 20de2d0f6..ab145a2d7 100644 --- a/internal/toproto6/action_schema_test.go +++ b/internal/toproto6/action_schema_test.go @@ -8,7 +8,6 @@ import ( "testing" "github.com/google/go-cmp/cmp" - "github.com/hashicorp/terraform-plugin-framework/action/schema" actionschema "github.com/hashicorp/terraform-plugin-framework/action/schema" "github.com/hashicorp/terraform-plugin-framework/internal/toproto6" "github.com/hashicorp/terraform-plugin-go/tfprotov6" @@ -40,7 +39,7 @@ func TestActionSchema(t *testing.T) { }, }, Blocks: map[string]actionschema.Block{ - "single_block": schema.SingleNestedBlock{ + "single_block": actionschema.SingleNestedBlock{ Attributes: map[string]actionschema.Attribute{ "bool": actionschema.BoolAttribute{ Required: true,