Skip to content

Commit a8477a1

Browse files
authored
tfprotov5+tfprotov6: Add protocol support for planning/invoking actions (#534)
* exported types first for protov5 * update proto to be config * fromproto and toproto * add to protov5 server * from proto tests * toproto tests * logging test * package docs * protov6 * mention provider prefix
1 parent 0e22029 commit a8477a1

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+3901
-56
lines changed

internal/logging/context.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,15 @@ func ListResourceContext(ctx context.Context, listResource string) context.Conte
100100
return ctx
101101
}
102102

103+
// ActionContext injects the action type into logger contexts.
104+
func ActionContext(ctx context.Context, action string) context.Context {
105+
ctx = tfsdklog.SetField(ctx, KeyActionType, action)
106+
ctx = tfsdklog.SubsystemSetField(ctx, SubsystemProto, KeyActionType, action)
107+
ctx = tflog.SetField(ctx, KeyActionType, action)
108+
109+
return ctx
110+
}
111+
103112
// RpcContext injects the RPC name into logger contexts.
104113
func RpcContext(ctx context.Context, rpc string) context.Context {
105114
ctx = tfsdklog.SetField(ctx, KeyRPC, rpc)

internal/logging/keys.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ const (
6363
// The type of list resource being operated on
6464
KeyListResourceType = "tf_list_resource_type"
6565

66+
// The action being operated on
67+
KeyActionType = "tf_action_type"
68+
6669
// Path to protocol data file, such as "/tmp/example.json"
6770
KeyProtocolDataFile = "tf_proto_data_file"
6871

tfprotov5/action.go

Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package tfprotov5
5+
6+
import (
7+
"context"
8+
"iter"
9+
)
10+
11+
// ActionMetadata describes metadata for an action in the GetMetadata RPC.
12+
type ActionMetadata struct {
13+
// TypeName is the name of the action.
14+
TypeName string
15+
}
16+
17+
// ActionServer is an interface containing the methods an action implementation needs to fill.
18+
type ActionServer interface {
19+
// PlanAction is called when Terraform is attempting to
20+
// calculate a plan for an action. Depending on the type defined in
21+
// the action schema, Terraform may also pass the plan of linked resources
22+
// that the action can modify or return unmodified to influence Terraform's plan.
23+
PlanAction(context.Context, *PlanActionRequest) (*PlanActionResponse, error)
24+
25+
// InvokeAction is called when Terraform wants to execute the logic of an action.
26+
// Depending on the type defined in the action schema, Terraform may also pass the
27+
// state of linked resources. The provider runs the logic of the action, reporting progress
28+
// events as desired, then sends a final complete event that has the linked resource's resulting
29+
// state and identity.
30+
//
31+
// If an error occurs, the provider sends a complete event with the relevant diagnostics.
32+
InvokeAction(context.Context, *InvokeActionRequest) (*InvokeActionServerStream, error)
33+
}
34+
35+
// PlanActionRequest is the request Terraform sends when it is attempting to
36+
// calculate a plan for an action.
37+
type PlanActionRequest struct {
38+
// ActionType is the name of the action being called.
39+
ActionType string
40+
41+
// LinkedResources contains the data of the managed resource types that are linked to this action.
42+
//
43+
// - If the action schema type is Unlinked, this field will be empty.
44+
// - If the action schema type is Lifecycle, this field will be contain a single linked resource.
45+
// - If the action schema type is Linked, this field will be one or more linked resources, which
46+
// will be in the same order as the linked resource schemas are defined in the action schema.
47+
//
48+
// For Lifecycle actions, the provider may only change computed-only attributes.
49+
//
50+
// For Linked actions, the provider may change any attributes as long as it adheres to the resource schema.
51+
LinkedResources []*ProposedLinkedResource
52+
53+
// Config is the configuration the user supplied for the action. See
54+
// the documentation on `DynamicValue` for more information about
55+
// safely accessing the configuration.
56+
Config *DynamicValue
57+
58+
// ClientCapabilities defines optionally supported protocol features for the
59+
// PlanAction RPC, such as forward-compatible Terraform behavior changes.
60+
ClientCapabilities *PlanActionClientCapabilities
61+
}
62+
63+
// ProposedLinkedResource represents linked resource data before PlanAction is called.
64+
type ProposedLinkedResource struct {
65+
// PriorState is the state of the linked resource before the plan is applied,
66+
// represented as a `DynamicValue`. See the documentation for
67+
// `DynamicValue` for information about safely accessing the state.
68+
PriorState *DynamicValue
69+
70+
// PlannedState is the latest indication of what the state for the
71+
// linked resource should be after apply, represented as a `DynamicValue`.
72+
// See the documentation for `DynamicValue` for information about safely
73+
// accessing the planned state.
74+
//
75+
// Since PlannedState is the most recent plan for the linked resource, it could
76+
// be the result of an RPC call to PlanResourceChange or an RPC call to PlanAction
77+
// for a predecessor action
78+
PlannedState *DynamicValue
79+
80+
// Config is the configuration the user supplied for the linked resource. See
81+
// the documentation on `DynamicValue` for more information about
82+
// safely accessing the configuration.
83+
Config *DynamicValue
84+
85+
// PriorIdentity is the identity of the resource before the plan is
86+
// applied, represented as a `ResourceIdentityData`.
87+
PriorIdentity *ResourceIdentityData
88+
}
89+
90+
// PlanActionResponse is the response from the provider when planning an action. If the action
91+
// has linked resources, it will contain any modifications made to the planned state or identity.
92+
type PlanActionResponse struct {
93+
// LinkedResources contains the provider modified data of the managed resource types that are linked to this action.
94+
//
95+
// For Lifecycle actions, the provider may only change computed-only attributes.
96+
//
97+
// For Linked actions, the provider may change any attributes as long as it adheres to the resource schema.
98+
LinkedResources []*PlannedLinkedResource
99+
100+
// Diagnostics report errors or warnings related to plannning the action and calculating
101+
// the planned state of the requested linked resources. Returning an empty slice
102+
// indicates a successful validation with no warnings or errors generated.
103+
Diagnostics []*Diagnostic
104+
105+
// Deferred is used to indicate to Terraform that the PlanAction operation
106+
// needs to be deferred for a reason.
107+
Deferred *Deferred
108+
}
109+
110+
// PlannedLinkedResource represents linked resource data that was planned during PlanAction and returned.
111+
type PlannedLinkedResource struct {
112+
// PlannedState is the provider's indication of what the state for the
113+
// linked resource should be after apply, represented as a `DynamicValue`. See
114+
// the documentation for `DynamicValue` for information about safely
115+
// creating the `DynamicValue`.
116+
PlannedState *DynamicValue
117+
118+
// PlannedIdentity is the provider's indication of what the identity for the
119+
// linked resource should be after apply, represented as a `ResourceIdentityData`
120+
PlannedIdentity *ResourceIdentityData
121+
}
122+
123+
// InvokeActionRequest is the request Terraform sends when it wants to execute
124+
// the logic of an action.
125+
type InvokeActionRequest struct {
126+
// ActionType is the name of the action being called.
127+
ActionType string
128+
129+
// LinkedResources contains the data of the managed resource types that are linked to this action.
130+
//
131+
// - If the action schema type is Unlinked, this field will be empty.
132+
// - If the action schema type is Lifecycle, this field will be contain a single linked resource.
133+
// - If the action schema type is Linked, this field will be one or more linked resources, which
134+
// will be in the same order as the linked resource schemas are defined in the action schema.
135+
//
136+
// For Lifecycle actions, the provider may only change computed-only attributes.
137+
//
138+
// For Linked actions, the provider may change any attributes as long as it adheres to the resource schema.
139+
LinkedResources []*InvokeLinkedResource
140+
141+
// Config is the configuration the user supplied for the action. See
142+
// the documentation on `DynamicValue` for more information about
143+
// safely accessing the configuration.
144+
Config *DynamicValue
145+
}
146+
147+
// InvokeLinkedResource represents linked resource data before InvokeAction is called.
148+
type InvokeLinkedResource struct {
149+
// PriorState is the state of the linked resource before changes are applied,
150+
// represented as a `DynamicValue`. See the documentation for
151+
// `DynamicValue` for information about safely accessing the state.
152+
PriorState *DynamicValue
153+
154+
// PlannedState is the latest indication of what the state for the
155+
// linked resource should look like after changes are applied, represented
156+
// as a `DynamicValue`. See the documentation for `DynamicValue` for
157+
// information about safely accessing the planned state.
158+
//
159+
// Since PlannedState is the most recent state for the linked resource, it could
160+
// be the result of an RPC call to ApplyResourceChange or an RPC call to InvokeAction
161+
// for a predecessor action.
162+
PlannedState *DynamicValue
163+
164+
// Config is the configuration the user supplied for the linked resource. See
165+
// the documentation on `DynamicValue` for more information about
166+
// safely accessing the configuration.
167+
Config *DynamicValue
168+
169+
// PlannedIdentity is Terraform's plan for what the linked resource identity should
170+
// look like after the changes are applied, represented as a `ResourceIdentityData`.
171+
PlannedIdentity *ResourceIdentityData
172+
}
173+
174+
// InvokeActionServerStream represents a streaming response to an
175+
// InvokeActionRequest. An instance of this struct is supplied as an argument
176+
// to the provider's InvokeAction implementation. The provider should set an
177+
// Events iterator function that pushes zero or more events of type InvokeActionEvent.
178+
type InvokeActionServerStream struct {
179+
// Events is the iterator that the provider can stream progress messages back to Terraform
180+
// as the action is executing. Once the provider has completed the action invocation, the provider must
181+
// respond with a completed event with the new linked resource state or diagnostics explaining why
182+
// the action failed.
183+
Events iter.Seq[InvokeActionEvent]
184+
}
185+
186+
// InvokeActionEvent is an event sent back to Terraform during the InvokeAction RPC.
187+
type InvokeActionEvent struct {
188+
// Type is the type of event that is being sent back during InvokeAction, either a Progress event
189+
// or a Completed event.
190+
Type InvokeActionEventType
191+
}
192+
193+
// InvokeActionEventType is an intentionally unimplementable interface that
194+
// functions as an enum, allowing us to use different strongly-typed event types
195+
// that contain additional, but different data, as a generic "event" type.
196+
type InvokeActionEventType interface {
197+
isInvokeActionEventType() // this interface is only implementable in this package
198+
}
199+
200+
var (
201+
_ InvokeActionEventType = ProgressInvokeActionEventType{}
202+
_ InvokeActionEventType = CompletedInvokeActionEventType{}
203+
)
204+
205+
// ProgressInvokeActionEventType represents a progress update that should be displayed in the Terraform
206+
// CLI or external system running Terraform.
207+
type ProgressInvokeActionEventType struct {
208+
// Message is the human-readable message to display about the progress of the action invocation.
209+
Message string
210+
}
211+
212+
func (a ProgressInvokeActionEventType) isInvokeActionEventType() {}
213+
214+
// CompletedInvokeActionEventType represents the final completed event, along with all of the linked resource
215+
// data modified by the provider or diagnostics about an action invocation failure.
216+
type CompletedInvokeActionEventType struct {
217+
// LinkedResources contains the provider modified data of the managed resource types that are linked to this action.
218+
//
219+
// For Lifecycle actions, the provider may only change computed-only attributes.
220+
//
221+
// For Linked actions, the provider may change any attributes as long as it adheres to the resource schema.
222+
LinkedResources []*NewLinkedResource
223+
224+
// Diagnostics report errors or warnings related to invoking an action.
225+
// Returning an empty slice indicates a successful invocation with no warnings
226+
// or errors generated.
227+
Diagnostics []*Diagnostic
228+
}
229+
230+
func (a CompletedInvokeActionEventType) isInvokeActionEventType() {}
231+
232+
// NewLinkedResource represents linked resource data that was changed during InvokeAction and returned.
233+
//
234+
// Depending on how the action was invoked, the modified state data will either be immediately recorded in
235+
// state or reconcicled in a future terraform apply operation.
236+
type NewLinkedResource struct {
237+
// NewState is the provider's understanding of what the linked resource's
238+
// state is after changes are applied, represented as a `DynamicValue`.
239+
// See the documentation for `DynamicValue` for information about
240+
// safely creating the `DynamicValue`.
241+
//
242+
// Any attribute, whether computed or not, that has a known value in
243+
// the PlannedState in the InvokeActionRequest must be preserved
244+
// exactly as it was in NewState.
245+
NewState *DynamicValue
246+
247+
// NewIdentity is the provider's understanding of what the linked resource's
248+
// identity is after changes are applied, represented as a `ResourceIdentityData`.
249+
NewIdentity *ResourceIdentityData
250+
251+
// RequiresReplace can only be set if diagnostics are returned for the action and indicate
252+
// the linked resource must be replaced as a result of the action invocation error.
253+
RequiresReplace bool
254+
}

tfprotov5/action_schema.go

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package tfprotov5
5+
6+
// ActionSchema is how Terraform defines the shape of action data and
7+
// how the practitioner can interact with the action.
8+
type ActionSchema struct {
9+
// Schema is the definition for the action data itself, which will be specified in an action block in the user's configuration.
10+
Schema *Schema
11+
12+
// Type defines how a practitioner can trigger an action, as well as what effect the action can have on the state
13+
// of the linked managed resources. There are currently three different types of actions:
14+
// - Unlinked actions are actions that cannot cause changes to resource states.
15+
// - Lifecycle actions are actions that can cause changes to exactly one resource state.
16+
// - Linked actions are actions that can cause changes to one or more resource states.
17+
Type ActionSchemaType
18+
}
19+
20+
// ActionSchemaType is an intentionally unimplementable interface that
21+
// functions as an enum, allowing us to use different strongly-typed action schema types
22+
// that contain additional, but different data, as a generic "action" type.
23+
//
24+
// An action can only be one type (Unlinked, Lifecycle, or Linked), which are all statically defined in the protocol.
25+
type ActionSchemaType interface {
26+
isActionSchemaType()
27+
}
28+
29+
var (
30+
_ ActionSchemaType = UnlinkedActionSchemaType{}
31+
_ ActionSchemaType = LifecycleActionSchemaType{}
32+
_ ActionSchemaType = LinkedActionSchemaType{}
33+
)
34+
35+
// UnlinkedActionSchemaType represents an unlinked action, which cannot cause changes to resource states.
36+
type UnlinkedActionSchemaType struct{}
37+
38+
func (a UnlinkedActionSchemaType) isActionSchemaType() {}
39+
40+
// LifecycleActionSchemaType represents a lifecycle action, which can cause changes to exactly one resource state,
41+
// which is the linked resource.
42+
type LifecycleActionSchemaType struct {
43+
// Executes defines when the lifecycle action must be executed in relation to the linked resource, either before
44+
// or after the linked resource's plan/apply.
45+
Executes LifecycleExecutionOrder
46+
47+
// LinkedResource is the managed resource type that this action can make state changes to.
48+
// This linked resource is currently restricted to be defined in the same provider as the action is defined.
49+
LinkedResource *LinkedResourceSchema
50+
}
51+
52+
func (a LifecycleActionSchemaType) isActionSchemaType() {}
53+
54+
const (
55+
// LifecycleExecutionOrderInvalid is used to indicate an invalid `LifecycleExecutionOrder`.
56+
// Provider developers should not use it.
57+
LifecycleExecutionOrderInvalid LifecycleExecutionOrder = 0
58+
59+
// LifecycleExecutionOrderBefore is used to indicate that the action must be invoked before it's
60+
// linked resource's plan/apply.
61+
LifecycleExecutionOrderBefore LifecycleExecutionOrder = 1
62+
63+
// LifecycleExecutionOrderAfter is used to indicate that the action must be invoked after it's
64+
// linked resource's plan/apply.
65+
LifecycleExecutionOrderAfter LifecycleExecutionOrder = 2
66+
)
67+
68+
// LifecycleExecutionOrder is an enum that represents when an action is invoked relative to it's linked resource.
69+
type LifecycleExecutionOrder int32
70+
71+
func (l LifecycleExecutionOrder) String() string {
72+
switch l {
73+
case 0:
74+
return "INVALID"
75+
case 1:
76+
return "BEFORE"
77+
case 2:
78+
return "AFTER"
79+
case 3:
80+
}
81+
return "UNKNOWN"
82+
}
83+
84+
// LinkedResourceSchema represents information about the schema of a linked resource, which is used by an action schema to describe to Terraform the
85+
// resource types that an action is allowed to change the state of. Linked resources are currently restricted to be defined in the same provider
86+
// as the action is defined.
87+
//
88+
// LinkedResourceSchema does not contain the entire schema definition of the linked resource, which must be obtained by the provider in order to
89+
// decode the linked resource plan/state/identity protocol data during PlanAction and InvokeAction.
90+
type LinkedResourceSchema struct {
91+
// TypeName is the name of the managed resource which can have it's resource state changed by the action. The name should be prefixed with
92+
// the provider shortname and an underscore.
93+
TypeName string
94+
95+
// Description is a human-readable description of the linked resource.
96+
Description string
97+
}
98+
99+
// LinkedActionSchemaType represents a linked action, which can cause changes to one or more resource states.
100+
type LinkedActionSchemaType struct {
101+
// LinkedResources are the managed resource types that this action can make state changes to.
102+
// These linked resources are currently restricted to be defined in the same provider as the action is defined.
103+
LinkedResources []*LinkedResourceSchema
104+
}
105+
106+
func (a LinkedActionSchemaType) isActionSchemaType() {}

0 commit comments

Comments
 (0)