Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changelog/43972.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:new-action
aws_lambda_invoke
```
8 changes: 4 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ require (
github.com/hashicorp/go-multierror v1.1.1
github.com/hashicorp/go-uuid v1.0.3
github.com/hashicorp/go-version v1.7.0
github.com/hashicorp/hcl/v2 v2.23.0
github.com/hashicorp/hcl/v2 v2.24.0
github.com/hashicorp/terraform-json v0.27.2
github.com/hashicorp/terraform-plugin-framework v1.16.0
github.com/hashicorp/terraform-plugin-framework-jsontypes v0.2.0
Expand All @@ -300,7 +300,7 @@ require (
github.com/hashicorp/terraform-plugin-log v0.9.0
github.com/hashicorp/terraform-plugin-mux v0.21.0
github.com/hashicorp/terraform-plugin-sdk/v2 v2.37.0
github.com/hashicorp/terraform-plugin-testing v1.13.3
github.com/hashicorp/terraform-plugin-testing v1.13.3-0.20250909115916-1a2eeae85247
github.com/jaswdr/faker/v2 v2.8.0
github.com/jmespath/go-jmespath v0.4.0
github.com/mattbaird/jsonpatch v0.0.0-20240118010651-0ba75a80ca38
Expand Down Expand Up @@ -349,7 +349,7 @@ require (
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
github.com/hashicorp/hc-install v0.9.2 // indirect
github.com/hashicorp/logutils v1.0.0 // indirect
github.com/hashicorp/terraform-exec v0.23.0 // indirect
github.com/hashicorp/terraform-exec v0.23.1-0.20250717072919-061a850a52d2 // indirect
github.com/hashicorp/terraform-registry-address v0.4.0 // indirect
github.com/hashicorp/terraform-svchost v0.1.1 // indirect
github.com/hashicorp/yamux v0.1.2 // indirect
Expand All @@ -368,7 +368,7 @@ require (
github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/zclconf/go-cty v1.16.4 // indirect
github.com/zclconf/go-cty v1.17.0 // indirect
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
go.opentelemetry.io/contrib/instrumentation/github.com/aws/aws-sdk-go-v2/otelaws v0.63.0 // indirect
go.opentelemetry.io/otel v1.38.0 // indirect
Expand Down
16 changes: 8 additions & 8 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -661,12 +661,12 @@ github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKe
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/hc-install v0.9.2 h1:v80EtNX4fCVHqzL9Lg/2xkp62bbvQMnvPQ0G+OmtO24=
github.com/hashicorp/hc-install v0.9.2/go.mod h1:XUqBQNnuT4RsxoxiM9ZaUk0NX8hi2h+Lb6/c0OZnC/I=
github.com/hashicorp/hcl/v2 v2.23.0 h1:Fphj1/gCylPxHutVSEOf2fBOh1VE4AuLV7+kbJf3qos=
github.com/hashicorp/hcl/v2 v2.23.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA=
github.com/hashicorp/hcl/v2 v2.24.0 h1:2QJdZ454DSsYGoaE6QheQZjtKZSUs9Nh2izTWiwQxvE=
github.com/hashicorp/hcl/v2 v2.24.0/go.mod h1:oGoO1FIQYfn/AgyOhlg9qLC6/nOJPX3qGbkZpYAcqfM=
github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/hashicorp/terraform-exec v0.23.0 h1:MUiBM1s0CNlRFsCLJuM5wXZrzA3MnPYEsiXmzATMW/I=
github.com/hashicorp/terraform-exec v0.23.0/go.mod h1:mA+qnx1R8eePycfwKkCRk3Wy65mwInvlpAeOwmA7vlY=
github.com/hashicorp/terraform-exec v0.23.1-0.20250717072919-061a850a52d2 h1:90fcAqw0Qmv4vY7zL4jEKgKarHmOnNN6SjTY68eLKGA=
github.com/hashicorp/terraform-exec v0.23.1-0.20250717072919-061a850a52d2/go.mod h1:8D3RLLpzAZdhT9jvALYz1KHyGU4OvI73I1o0+01QJxA=
github.com/hashicorp/terraform-json v0.27.2 h1:BwGuzM6iUPqf9JYM/Z4AF1OJ5VVJEEzoKST/tRDBJKU=
github.com/hashicorp/terraform-json v0.27.2/go.mod h1:GzPLJ1PLdUG5xL6xn1OXWIjteQRT2CNT9o/6A9mi9hE=
github.com/hashicorp/terraform-plugin-framework v1.16.0 h1:tP0f+yJg0Z672e7levixDe5EpWwrTrNryPM9kDMYIpE=
Expand All @@ -685,8 +685,8 @@ github.com/hashicorp/terraform-plugin-mux v0.21.0 h1:QsEYnzSD2c3zT8zUrUGqaFGhV/Z
github.com/hashicorp/terraform-plugin-mux v0.21.0/go.mod h1:Qpt8+6AD7NmL0DS7ASkN0EXpDQ2J/FnnIgeUr1tzr5A=
github.com/hashicorp/terraform-plugin-sdk/v2 v2.37.0 h1:NFPMacTrY/IdcIcnUB+7hsore1ZaRWU9cnB6jFoBnIM=
github.com/hashicorp/terraform-plugin-sdk/v2 v2.37.0/go.mod h1:QYmYnLfsosrxjCnGY1p9c7Zj6n9thnEE+7RObeYs3fA=
github.com/hashicorp/terraform-plugin-testing v1.13.3 h1:QLi/khB8Z0a5L54AfPrHukFpnwsGL8cwwswj4RZduCo=
github.com/hashicorp/terraform-plugin-testing v1.13.3/go.mod h1:WHQ9FDdiLoneey2/QHpGM/6SAYf4A7AZazVg7230pLE=
github.com/hashicorp/terraform-plugin-testing v1.13.3-0.20250909115916-1a2eeae85247 h1:lA6ofPwmCXAX7J7kVP9t/WMU5+eA4e9YvJUiRLPdENw=
github.com/hashicorp/terraform-plugin-testing v1.13.3-0.20250909115916-1a2eeae85247/go.mod h1:4r/7cxl1mpskfALcq58Iyu5aPiTSco8SVrKkcLyP5g4=
github.com/hashicorp/terraform-registry-address v0.4.0 h1:S1yCGomj30Sao4l5BMPjTGZmCNzuv7/GDTDX99E9gTk=
github.com/hashicorp/terraform-registry-address v0.4.0/go.mod h1:LRS1Ay0+mAiRkUyltGT+UHWkIqTFvigGn/LbMshfflE=
github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ=
Expand Down Expand Up @@ -790,8 +790,8 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/zclconf/go-cty v1.16.4 h1:QGXaag7/7dCzb+odlGrgr+YmYZFaOCMW6DEpS+UD1eE=
github.com/zclconf/go-cty v1.16.4/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
github.com/zclconf/go-cty v1.17.0 h1:seZvECve6XX4tmnvRzWtJNHdscMtYEx5R7bnnVyd/d0=
github.com/zclconf/go-cty v1.17.0/go.mod h1:wqFzcImaLTI6A5HfsRwB0nj5n0MRZFwmey8YoFPPs3U=
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo=
github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
Expand Down
216 changes: 216 additions & 0 deletions internal/service/lambda/invoke_action.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,216 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package lambda

import (
"context"
"encoding/base64"
"fmt"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/service/lambda"
awstypes "github.com/aws/aws-sdk-go-v2/service/lambda/types"
"github.com/hashicorp/terraform-plugin-framework/action"
"github.com/hashicorp/terraform-plugin-framework/action/schema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/hashicorp/terraform-provider-aws/internal/framework"
fwtypes "github.com/hashicorp/terraform-provider-aws/internal/framework/types"
"github.com/hashicorp/terraform-provider-aws/internal/framework/validators"
"github.com/hashicorp/terraform-provider-aws/names"
)

// @Action(aws_lambda_invoke, name="Invoke")
func newInvokeAction(_ context.Context) (action.ActionWithConfigure, error) {
return &invokeAction{}, nil
}

var (
_ action.Action = (*invokeAction)(nil)
)

type invokeAction struct {
framework.ActionWithModel[invokeActionModel]
}

type invokeActionModel struct {
framework.WithRegionModel
FunctionName types.String `tfsdk:"function_name"`
Payload types.String `tfsdk:"payload"`
Qualifier types.String `tfsdk:"qualifier"`
InvocationType fwtypes.StringEnum[awstypes.InvocationType] `tfsdk:"invocation_type"`
LogType fwtypes.StringEnum[awstypes.LogType] `tfsdk:"log_type"`
ClientContext types.String `tfsdk:"client_context"`
}

func (a *invokeAction) Schema(ctx context.Context, req action.SchemaRequest, resp *action.SchemaResponse) {
resp.Schema = schema.Schema{
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.",
Attributes: map[string]schema.Attribute{
"function_name": schema.StringAttribute{
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).",
Required: true,
},
"payload": schema.StringAttribute{
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.",
Required: true,
Validators: []validator.String{
validators.JSON(),
},
},
"qualifier": schema.StringAttribute{
Description: "The version or alias of the Lambda function to invoke. If not specified, the $LATEST version will be invoked.",
Optional: true,
},
"invocation_type": schema.StringAttribute{
CustomType: fwtypes.StringEnumType[awstypes.InvocationType](),
Description: "The invocation type. Valid values are 'RequestResponse' (synchronous), 'Event' (asynchronous), and 'DryRun' (validate parameters without invoking). Defaults to 'RequestResponse'.",
Optional: true,
},
"log_type": schema.StringAttribute{
CustomType: fwtypes.StringEnumType[awstypes.LogType](),
Description: "Set to 'Tail' to include the execution log in the response. Only applies to synchronous invocations ('RequestResponse' invocation type). Defaults to 'None'.",
Optional: true,
},
"client_context": schema.StringAttribute{
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.",
Optional: true,
},
},
}
}

func (a *invokeAction) Invoke(ctx context.Context, req action.InvokeRequest, resp *action.InvokeResponse) {
var config invokeActionModel

// Parse configuration
resp.Diagnostics.Append(req.Config.Get(ctx, &config)...)
if resp.Diagnostics.HasError() {
return
}

// Get AWS client
conn := a.Meta().LambdaClient(ctx)

functionName := config.FunctionName.ValueString()
payload := config.Payload.ValueString()

// Set default values for optional parameters
invocationType := awstypes.InvocationTypeRequestResponse
if !config.InvocationType.IsNull() && !config.InvocationType.IsUnknown() {
invocationType = config.InvocationType.ValueEnum()
}

logType := awstypes.LogTypeNone
if !config.LogType.IsNull() && !config.LogType.IsUnknown() {
logType = config.LogType.ValueEnum()
}

tflog.Info(ctx, "Starting Lambda function invocation action", map[string]any{
"function_name": functionName,
"invocation_type": string(invocationType),
"log_type": string(logType),
"payload_length": len(payload),
"has_qualifier": !config.Qualifier.IsNull(),
"has_client_context": !config.ClientContext.IsNull(),
})

// Send initial progress update
resp.SendProgress(action.InvokeProgressEvent{
Message: fmt.Sprintf("Invoking Lambda function %s...", functionName),
})

// Build the invoke input
input := &lambda.InvokeInput{
FunctionName: aws.String(functionName),
Payload: []byte(payload),
InvocationType: invocationType,
LogType: logType,
}

// Set optional parameters
if !config.Qualifier.IsNull() {
input.Qualifier = config.Qualifier.ValueStringPointer()
}

if !config.ClientContext.IsNull() {
clientContext := config.ClientContext.ValueString()
// Validate that client context is base64 encoded
if _, err := base64.StdEncoding.DecodeString(clientContext); err != nil {
resp.Diagnostics.AddError(
"Invalid Client Context",
fmt.Sprintf("Client context must be base64 encoded: %s", err),
)
return
}
input.ClientContext = aws.String(clientContext)
}

// Perform the invocation
output, err := conn.Invoke(ctx, input)
if err != nil {
resp.Diagnostics.AddError(
"Failed to Invoke Lambda Function",
fmt.Sprintf("Could not invoke Lambda function %s: %s", functionName, err),
)
return
}

// Handle function errors
if output.FunctionError != nil {
functionError := aws.ToString(output.FunctionError)
payloadStr := string(output.Payload)

resp.Diagnostics.AddError(
"Lambda Function Execution Error",
fmt.Sprintf("Lambda function %s returned an error (%s): %s", functionName, functionError, payloadStr),
)
return
}

// Handle different invocation types
switch invocationType {
case awstypes.InvocationTypeRequestResponse:
// For synchronous invocations, we get an immediate response
statusCode := output.StatusCode
payloadLength := len(output.Payload)

var message string
if logType == awstypes.LogTypeTail && output.LogResult != nil {
message = fmt.Sprintf("Lambda function %s invoked successfully (status: %d, payload: %d bytes, logs included)",
functionName, statusCode, payloadLength)
} else {
message = fmt.Sprintf("Lambda function %s invoked successfully (status: %d, payload: %d bytes)",
functionName, statusCode, payloadLength)
}

resp.SendProgress(action.InvokeProgressEvent{
Message: message,
})

case awstypes.InvocationTypeEvent:
// For asynchronous invocations, we only get confirmation that the request was accepted
statusCode := output.StatusCode
resp.SendProgress(action.InvokeProgressEvent{
Message: fmt.Sprintf("Lambda function %s invoked asynchronously (status: %d)", functionName, statusCode),
})

case awstypes.InvocationTypeDryRun:
// For dry run, we validate parameters without actually invoking
statusCode := output.StatusCode
resp.SendProgress(action.InvokeProgressEvent{
Message: fmt.Sprintf("Lambda function %s dry run completed successfully (status: %d)", functionName, statusCode),
})
}

tflog.Info(ctx, "Lambda function invocation action completed successfully", map[string]any{
"function_name": functionName,
"invocation_type": string(invocationType),
names.AttrStatusCode: output.StatusCode,
"executed_version": aws.ToString(output.ExecutedVersion),
"has_logs": output.LogResult != nil,
"payload_length": len(output.Payload),
})
}
Loading
Loading