Skip to content

Commit 885afe5

Browse files
authored
Merge pull request #43972 from hashicorp/f-lambda-invoke-action
New action: `aws_lambda_invoke`
2 parents 2604401 + e24fef8 commit 885afe5

File tree

5 files changed

+1011
-0
lines changed

5 files changed

+1011
-0
lines changed

.changelog/43972.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:new-action
2+
aws_lambda_invoke
3+
```
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package lambda
5+
6+
import (
7+
"context"
8+
"encoding/base64"
9+
"fmt"
10+
11+
"github.com/aws/aws-sdk-go-v2/aws"
12+
"github.com/aws/aws-sdk-go-v2/service/lambda"
13+
awstypes "github.com/aws/aws-sdk-go-v2/service/lambda/types"
14+
"github.com/hashicorp/terraform-plugin-framework/action"
15+
"github.com/hashicorp/terraform-plugin-framework/action/schema"
16+
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
17+
"github.com/hashicorp/terraform-plugin-framework/types"
18+
"github.com/hashicorp/terraform-plugin-log/tflog"
19+
"github.com/hashicorp/terraform-provider-aws/internal/framework"
20+
fwtypes "github.com/hashicorp/terraform-provider-aws/internal/framework/types"
21+
"github.com/hashicorp/terraform-provider-aws/internal/framework/validators"
22+
"github.com/hashicorp/terraform-provider-aws/names"
23+
)
24+
25+
// @Action(aws_lambda_invoke, name="Invoke")
26+
func newInvokeAction(_ context.Context) (action.ActionWithConfigure, error) {
27+
return &invokeAction{}, nil
28+
}
29+
30+
var (
31+
_ action.Action = (*invokeAction)(nil)
32+
)
33+
34+
type invokeAction struct {
35+
framework.ActionWithModel[invokeActionModel]
36+
}
37+
38+
type invokeActionModel struct {
39+
framework.WithRegionModel
40+
FunctionName types.String `tfsdk:"function_name"`
41+
Payload types.String `tfsdk:"payload"`
42+
Qualifier types.String `tfsdk:"qualifier"`
43+
InvocationType fwtypes.StringEnum[awstypes.InvocationType] `tfsdk:"invocation_type"`
44+
LogType fwtypes.StringEnum[awstypes.LogType] `tfsdk:"log_type"`
45+
ClientContext types.String `tfsdk:"client_context"`
46+
}
47+
48+
func (a *invokeAction) Schema(ctx context.Context, req action.SchemaRequest, resp *action.SchemaResponse) {
49+
resp.Schema = schema.Schema{
50+
Description: "Invokes an AWS Lambda function with the specified payload. This action allows for imperative invocation of Lambda functions with full control over invocation parameters.",
51+
Attributes: map[string]schema.Attribute{
52+
"function_name": schema.StringAttribute{
53+
Description: "The name, ARN, or partial ARN of the Lambda function to invoke. You can specify a function name (e.g., my-function), a qualified function name (e.g., my-function:PROD), or a partial ARN (e.g., 123456789012:function:my-function).",
54+
Required: true,
55+
},
56+
"payload": schema.StringAttribute{
57+
Description: "The JSON payload to send to the Lambda function. This should be a valid JSON string that represents the event data for your function.",
58+
Required: true,
59+
Validators: []validator.String{
60+
validators.JSON(),
61+
},
62+
},
63+
"qualifier": schema.StringAttribute{
64+
Description: "The version or alias of the Lambda function to invoke. If not specified, the $LATEST version will be invoked.",
65+
Optional: true,
66+
},
67+
"invocation_type": schema.StringAttribute{
68+
CustomType: fwtypes.StringEnumType[awstypes.InvocationType](),
69+
Description: "The invocation type. Valid values are 'RequestResponse' (synchronous), 'Event' (asynchronous), and 'DryRun' (validate parameters without invoking). Defaults to 'RequestResponse'.",
70+
Optional: true,
71+
},
72+
"log_type": schema.StringAttribute{
73+
CustomType: fwtypes.StringEnumType[awstypes.LogType](),
74+
Description: "Set to 'Tail' to include the execution log in the response. Only applies to synchronous invocations ('RequestResponse' invocation type). Defaults to 'None'.",
75+
Optional: true,
76+
},
77+
"client_context": schema.StringAttribute{
78+
Description: "Up to 3,583 bytes of base64-encoded data about the invoking client to pass to the function in the context object. This is only used for mobile applications.",
79+
Optional: true,
80+
},
81+
},
82+
}
83+
}
84+
85+
func (a *invokeAction) Invoke(ctx context.Context, req action.InvokeRequest, resp *action.InvokeResponse) {
86+
var config invokeActionModel
87+
88+
// Parse configuration
89+
resp.Diagnostics.Append(req.Config.Get(ctx, &config)...)
90+
if resp.Diagnostics.HasError() {
91+
return
92+
}
93+
94+
// Get AWS client
95+
conn := a.Meta().LambdaClient(ctx)
96+
97+
functionName := config.FunctionName.ValueString()
98+
payload := config.Payload.ValueString()
99+
100+
// Set default values for optional parameters
101+
invocationType := awstypes.InvocationTypeRequestResponse
102+
if !config.InvocationType.IsNull() && !config.InvocationType.IsUnknown() {
103+
invocationType = config.InvocationType.ValueEnum()
104+
}
105+
106+
logType := awstypes.LogTypeNone
107+
if !config.LogType.IsNull() && !config.LogType.IsUnknown() {
108+
logType = config.LogType.ValueEnum()
109+
}
110+
111+
tflog.Info(ctx, "Starting Lambda function invocation action", map[string]any{
112+
"function_name": functionName,
113+
"invocation_type": string(invocationType),
114+
"log_type": string(logType),
115+
"payload_length": len(payload),
116+
"has_qualifier": !config.Qualifier.IsNull(),
117+
"has_client_context": !config.ClientContext.IsNull(),
118+
})
119+
120+
// Send initial progress update
121+
resp.SendProgress(action.InvokeProgressEvent{
122+
Message: fmt.Sprintf("Invoking Lambda function %s...", functionName),
123+
})
124+
125+
// Build the invoke input
126+
input := &lambda.InvokeInput{
127+
FunctionName: aws.String(functionName),
128+
Payload: []byte(payload),
129+
InvocationType: invocationType,
130+
LogType: logType,
131+
}
132+
133+
// Set optional parameters
134+
if !config.Qualifier.IsNull() {
135+
input.Qualifier = config.Qualifier.ValueStringPointer()
136+
}
137+
138+
if !config.ClientContext.IsNull() {
139+
clientContext := config.ClientContext.ValueString()
140+
// Validate that client context is base64 encoded
141+
if _, err := base64.StdEncoding.DecodeString(clientContext); err != nil {
142+
resp.Diagnostics.AddError(
143+
"Invalid Client Context",
144+
fmt.Sprintf("Client context must be base64 encoded: %s", err),
145+
)
146+
return
147+
}
148+
input.ClientContext = aws.String(clientContext)
149+
}
150+
151+
// Perform the invocation
152+
output, err := conn.Invoke(ctx, input)
153+
if err != nil {
154+
resp.Diagnostics.AddError(
155+
"Failed to Invoke Lambda Function",
156+
fmt.Sprintf("Could not invoke Lambda function %s: %s", functionName, err),
157+
)
158+
return
159+
}
160+
161+
// Handle function errors
162+
if output.FunctionError != nil {
163+
functionError := aws.ToString(output.FunctionError)
164+
payloadStr := string(output.Payload)
165+
166+
resp.Diagnostics.AddError(
167+
"Lambda Function Execution Error",
168+
fmt.Sprintf("Lambda function %s returned an error (%s): %s", functionName, functionError, payloadStr),
169+
)
170+
return
171+
}
172+
173+
// Handle different invocation types
174+
switch invocationType {
175+
case awstypes.InvocationTypeRequestResponse:
176+
// For synchronous invocations, we get an immediate response
177+
statusCode := output.StatusCode
178+
payloadLength := len(output.Payload)
179+
180+
var message string
181+
if logType == awstypes.LogTypeTail && output.LogResult != nil {
182+
message = fmt.Sprintf("Lambda function %s invoked successfully (status: %d, payload: %d bytes, logs included)",
183+
functionName, statusCode, payloadLength)
184+
} else {
185+
message = fmt.Sprintf("Lambda function %s invoked successfully (status: %d, payload: %d bytes)",
186+
functionName, statusCode, payloadLength)
187+
}
188+
189+
resp.SendProgress(action.InvokeProgressEvent{
190+
Message: message,
191+
})
192+
193+
case awstypes.InvocationTypeEvent:
194+
// For asynchronous invocations, we only get confirmation that the request was accepted
195+
statusCode := output.StatusCode
196+
resp.SendProgress(action.InvokeProgressEvent{
197+
Message: fmt.Sprintf("Lambda function %s invoked asynchronously (status: %d)", functionName, statusCode),
198+
})
199+
200+
case awstypes.InvocationTypeDryRun:
201+
// For dry run, we validate parameters without actually invoking
202+
statusCode := output.StatusCode
203+
resp.SendProgress(action.InvokeProgressEvent{
204+
Message: fmt.Sprintf("Lambda function %s dry run completed successfully (status: %d)", functionName, statusCode),
205+
})
206+
}
207+
208+
tflog.Info(ctx, "Lambda function invocation action completed successfully", map[string]any{
209+
"function_name": functionName,
210+
"invocation_type": string(invocationType),
211+
names.AttrStatusCode: output.StatusCode,
212+
"executed_version": aws.ToString(output.ExecutedVersion),
213+
"has_logs": output.LogResult != nil,
214+
"payload_length": len(output.Payload),
215+
})
216+
}

0 commit comments

Comments
 (0)