Skip to content

Commit bf10880

Browse files
committed
protov5 and fwserver impl
1 parent 5eeac4c commit bf10880

22 files changed

+597
-4
lines changed

action/action.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package action
5+
6+
import "context"
7+
8+
type Action interface {
9+
// Schema should return the schema for this action.
10+
Schema(context.Context, SchemaRequest, *SchemaResponse)
11+
12+
// Metadata should return the full name of the action, such as examplecloud_do_thing.
13+
Metadata(context.Context, MetadataRequest, *MetadataResponse)
14+
15+
// TODO:Actions: Eventual landing place for all required methods to implement for an action
16+
}

action/doc.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
// TODO:Actions: Eventual package docs for actions
5+
package action

action/metadata.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package action
5+
6+
// MetadataRequest represents a request for the Action to return metadata,
7+
// such as its type name. An instance of this request struct is supplied as
8+
// an argument to the Action type Metadata method.
9+
type MetadataRequest struct {
10+
// ProviderTypeName is the string returned from
11+
// [provider.MetadataResponse.TypeName], if the Provider type implements
12+
// the Metadata method. This string should prefix the Action type name
13+
// with an underscore in the response.
14+
ProviderTypeName string
15+
}
16+
17+
// MetadataResponse represents a response to a MetadataRequest. An
18+
// instance of this response struct is supplied as an argument to the
19+
// Action type Metadata method.
20+
type MetadataResponse struct {
21+
// TypeName should be the full action type, including the provider
22+
// type prefix and an underscore. For example, examplecloud_thing.
23+
TypeName string
24+
}

action/schema.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package action
5+
6+
import (
7+
"github.com/hashicorp/terraform-plugin-framework/diag"
8+
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
9+
)
10+
11+
// SchemaRequest represents a request for the Action to return its schema.
12+
// An instance of this request struct is supplied as an argument to the
13+
// Action type Schema method.
14+
type SchemaRequest struct{}
15+
16+
// SchemaResponse represents a response to a SchemaRequest. An instance of this
17+
// response struct is supplied as an argument to the Action type Schema
18+
// method.
19+
type SchemaResponse struct {
20+
// TODO:Actions: This will eventually be replaced by an interface defined in
21+
// an "actions/schema" package. Schema implementations that will fulfill this
22+
// interface will be unlinked, linked, or lifecycle. (also defined in the "actions/schema" package)
23+
Schema fwschema.Schema
24+
25+
// Diagnostics report errors or warnings related to retrieving the action schema.
26+
// An empty slice indicates success, with no warnings or errors generated.
27+
Diagnostics diag.Diagnostics
28+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package fromproto5
5+
6+
import (
7+
"context"
8+
9+
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
10+
11+
"github.com/hashicorp/terraform-plugin-framework/action"
12+
"github.com/hashicorp/terraform-plugin-framework/diag"
13+
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
14+
"github.com/hashicorp/terraform-plugin-framework/internal/fwserver"
15+
)
16+
17+
// InvokeActionRequest returns the *fwserver.InvokeActionRequest equivalent of a *tfprotov5.InvokeActionRequest.
18+
func InvokeActionRequest(ctx context.Context, proto5 *tfprotov5.InvokeActionRequest, reqAction action.Action, actionSchema fwschema.Schema) (*fwserver.InvokeActionRequest, diag.Diagnostics) {
19+
if proto5 == nil {
20+
return nil, nil
21+
}
22+
23+
var diags diag.Diagnostics
24+
25+
// Panic prevention here to simplify the calling implementations.
26+
// This should not happen, but just in case.
27+
if actionSchema == nil {
28+
diags.AddError(
29+
"Missing Action Schema",
30+
"An unexpected error was encountered when handling the request. "+
31+
"This is always an issue in terraform-plugin-framework used to implement the provider and should be reported to the provider developers.\n\n"+
32+
"Please report this to the provider developer:\n\n"+
33+
"Missing schema.",
34+
)
35+
36+
return nil, diags
37+
}
38+
39+
fw := &fwserver.InvokeActionRequest{
40+
ActionSchema: actionSchema,
41+
}
42+
43+
config, configDiags := Config(ctx, proto5.Config, actionSchema)
44+
45+
diags.Append(configDiags...)
46+
47+
fw.Config = config
48+
49+
// TODO:Actions: Here we need to retrieve linked resource data
50+
51+
return fw, diags
52+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package fromproto5_test
5+
6+
// TODO:Actions: Add unit tests once this mapping logic is complete

internal/fromproto5/planaction.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package fromproto5
5+
6+
import (
7+
"context"
8+
9+
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
10+
11+
"github.com/hashicorp/terraform-plugin-framework/action"
12+
"github.com/hashicorp/terraform-plugin-framework/diag"
13+
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
14+
"github.com/hashicorp/terraform-plugin-framework/internal/fwserver"
15+
)
16+
17+
// PlanActionRequest returns the *fwserver.PlanActionRequest equivalent of a *tfprotov5.PlanActionRequest.
18+
func PlanActionRequest(ctx context.Context, proto5 *tfprotov5.PlanActionRequest, reqAction action.Action, actionSchema fwschema.Schema) (*fwserver.PlanActionRequest, diag.Diagnostics) {
19+
if proto5 == nil {
20+
return nil, nil
21+
}
22+
23+
var diags diag.Diagnostics
24+
25+
// Panic prevention here to simplify the calling implementations.
26+
// This should not happen, but just in case.
27+
if actionSchema == nil {
28+
diags.AddError(
29+
"Missing Action Schema",
30+
"An unexpected error was encountered when handling the request. "+
31+
"This is always an issue in terraform-plugin-framework used to implement the provider and should be reported to the provider developers.\n\n"+
32+
"Please report this to the provider developer:\n\n"+
33+
"Missing schema.",
34+
)
35+
36+
return nil, diags
37+
}
38+
39+
fw := &fwserver.PlanActionRequest{
40+
ActionSchema: actionSchema,
41+
}
42+
43+
config, configDiags := Config(ctx, proto5.Config, actionSchema)
44+
45+
diags.Append(configDiags...)
46+
47+
fw.Config = config
48+
49+
// TODO:Actions: Here we need to retrieve client capabilities and linked resource data
50+
51+
return fw, diags
52+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package fromproto5_test
5+
6+
// TODO:Actions: Add unit tests once this mapping logic is complete

internal/fwserver/server.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"fmt"
99
"sync"
1010

11+
"github.com/hashicorp/terraform-plugin-framework/action"
1112
"github.com/hashicorp/terraform-plugin-framework/datasource"
1213
"github.com/hashicorp/terraform-plugin-framework/diag"
1314
"github.com/hashicorp/terraform-plugin-framework/ephemeral"
@@ -40,6 +41,29 @@ type Server struct {
4041
// to [ephemeral.ConfigureRequest.ProviderData].
4142
EphemeralResourceConfigureData any
4243

44+
// actionSchemas is the cached Action Schemas for RPCs that need to
45+
// convert configuration data from the protocol. If not found, it will be
46+
// fetched from the Action.Schema() method.
47+
actionSchemas map[string]fwschema.Schema
48+
49+
// actionSchemasMutex is a mutex to protect concurrent actionSchemas
50+
// access from race conditions.
51+
actionSchemasMutex sync.RWMutex
52+
53+
// actionFuncs is the cached Action functions for RPCs that need to
54+
// access actions. If not found, it will be fetched from the
55+
// Provider.Actions() method.
56+
actionFuncs map[string]func() action.Action
57+
58+
// actionFuncsDiags is the cached Diagnostics obtained while populating
59+
// actionFuncs. This is to ensure any warnings or errors are also
60+
// returned appropriately when fetching actionFuncs.
61+
actionFuncsDiags diag.Diagnostics
62+
63+
// actionFuncsMutex is a mutex to protect concurrent actionFuncs
64+
// access from race conditions.
65+
actionFuncsMutex sync.Mutex
66+
4367
// dataSourceSchemas is the cached DataSource Schemas for RPCs that need to
4468
// convert configuration data from the protocol. If not found, it will be
4569
// fetched from the DataSourceType.GetSchema() method.
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package fwserver
5+
6+
import (
7+
"context"
8+
"fmt"
9+
10+
"github.com/hashicorp/terraform-plugin-framework/action"
11+
"github.com/hashicorp/terraform-plugin-framework/diag"
12+
"github.com/hashicorp/terraform-plugin-framework/internal/fwschema"
13+
"github.com/hashicorp/terraform-plugin-framework/internal/logging"
14+
"github.com/hashicorp/terraform-plugin-framework/provider"
15+
)
16+
17+
// Action returns the Action for a given action type.
18+
func (s *Server) Action(ctx context.Context, actionType string) (action.Action, diag.Diagnostics) {
19+
actionFuncs, diags := s.ActionFuncs(ctx)
20+
21+
actionFunc, ok := actionFuncs[actionType]
22+
23+
if !ok {
24+
diags.AddError(
25+
"Action Type Not Found",
26+
fmt.Sprintf("No action type named %q was found in the provider.", actionType),
27+
)
28+
29+
return nil, diags
30+
}
31+
32+
return actionFunc(), diags
33+
}
34+
35+
// ActionFuncs returns a map of Action functions. The results are cached
36+
// on first use.
37+
func (s *Server) ActionFuncs(ctx context.Context) (map[string]func() action.Action, diag.Diagnostics) {
38+
logging.FrameworkTrace(ctx, "Checking ActionFuncs lock")
39+
s.actionFuncsMutex.Lock()
40+
defer s.actionFuncsMutex.Unlock()
41+
42+
if s.actionFuncs != nil {
43+
return s.actionFuncs, s.actionFuncsDiags
44+
}
45+
46+
providerTypeName := s.ProviderTypeName(ctx)
47+
s.actionFuncs = make(map[string]func() action.Action)
48+
49+
provider, ok := s.Provider.(provider.ProviderWithActions)
50+
if !ok {
51+
// Only action specific RPCs should return diagnostics about the
52+
// provider not implementing actions or missing actions.
53+
return s.actionFuncs, s.actionFuncsDiags
54+
}
55+
56+
logging.FrameworkTrace(ctx, "Calling provider defined Provider Actions")
57+
actionFuncsSlice := provider.Actions(ctx)
58+
logging.FrameworkTrace(ctx, "Called provider defined Provider Actions")
59+
60+
for _, actionFunc := range actionFuncsSlice {
61+
actionImpl := actionFunc()
62+
63+
actionTypeReq := action.MetadataRequest{
64+
ProviderTypeName: providerTypeName,
65+
}
66+
actionTypeResp := action.MetadataResponse{}
67+
68+
actionImpl.Metadata(ctx, actionTypeReq, &actionTypeResp)
69+
70+
if actionTypeResp.TypeName == "" {
71+
s.actionFuncsDiags.AddError(
72+
"Action Type Missing",
73+
fmt.Sprintf("The %T Action returned an empty string from the Metadata method. ", actionImpl)+
74+
"This is always an issue with the provider and should be reported to the provider developers.",
75+
)
76+
continue
77+
}
78+
79+
logging.FrameworkTrace(ctx, "Found action", map[string]interface{}{logging.KeyActionType: actionTypeResp.TypeName})
80+
81+
if _, ok := s.actionFuncs[actionTypeResp.TypeName]; ok {
82+
s.actionFuncsDiags.AddError(
83+
"Duplicate Action Defined",
84+
fmt.Sprintf("The %s action type was returned for multiple actions. ", actionTypeResp.TypeName)+
85+
"Action types must be unique. "+
86+
"This is always an issue with the provider and should be reported to the provider developers.",
87+
)
88+
continue
89+
}
90+
91+
s.actionFuncs[actionTypeResp.TypeName] = actionFunc
92+
}
93+
94+
return s.actionFuncs, s.actionFuncsDiags
95+
}
96+
97+
// ActionSchema returns the Action Schema for the given type name and
98+
// caches the result for later Action operations.
99+
func (s *Server) ActionSchema(ctx context.Context, actionType string) (fwschema.Schema, diag.Diagnostics) {
100+
s.actionSchemasMutex.RLock()
101+
actionSchema, ok := s.actionSchemas[actionType]
102+
s.actionSchemasMutex.RUnlock()
103+
104+
if ok {
105+
return actionSchema, nil
106+
}
107+
108+
var diags diag.Diagnostics
109+
110+
actionImpl, actionDiags := s.Action(ctx, actionType)
111+
112+
diags.Append(actionDiags...)
113+
114+
if diags.HasError() {
115+
return nil, diags
116+
}
117+
118+
schemaReq := action.SchemaRequest{}
119+
schemaResp := action.SchemaResponse{}
120+
121+
logging.FrameworkTrace(ctx, "Calling provider defined Action Schema method", map[string]interface{}{logging.KeyActionType: actionType})
122+
actionImpl.Schema(ctx, schemaReq, &schemaResp)
123+
logging.FrameworkTrace(ctx, "Called provider defined Action Schema method", map[string]interface{}{logging.KeyActionType: actionType})
124+
125+
diags.Append(schemaResp.Diagnostics...)
126+
127+
if diags.HasError() {
128+
return schemaResp.Schema, diags
129+
}
130+
131+
s.actionSchemasMutex.Lock()
132+
133+
if s.actionSchemas == nil {
134+
s.actionSchemas = make(map[string]fwschema.Schema)
135+
}
136+
137+
s.actionSchemas[actionType] = schemaResp.Schema
138+
139+
s.actionSchemasMutex.Unlock()
140+
141+
return schemaResp.Schema, diags
142+
}

0 commit comments

Comments
 (0)