Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lambda: Ephemeral resource aws_lambda_invocation #39988

Merged
merged 25 commits into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
6503d5e
docs: Add ephemeral Lambda invocation
YakDriver Nov 1, 2024
35b3ddf
lambda: Add ephemeral invocation
YakDriver Nov 1, 2024
c739cb0
lambda/tests: Basic test
YakDriver Nov 1, 2024
338f027
make gen
YakDriver Nov 1, 2024
c2e1e8e
Fix docs
YakDriver Nov 1, 2024
fff03d0
lambda: Add arguments to ephemeral invocation
YakDriver Nov 1, 2024
7315148
Upgrade tools dependencies
YakDriver Nov 1, 2024
adb0704
Update flex with string-[]byte conversion
YakDriver Nov 6, 2024
c518129
Add actions for ephemerals
YakDriver Nov 6, 2024
37cc05d
invoke/ephemeral: Hold on plan open
YakDriver Nov 6, 2024
e3f3137
docs/lambda: Add attributes
YakDriver Nov 6, 2024
5c42f45
invocation/eph: Adjust test
YakDriver Nov 6, 2024
4510689
Merge remote-tracking branch 'origin/main' into f-ephemeral-lambda-in…
YakDriver Nov 6, 2024
ddf138f
Unneeded depends on
YakDriver Nov 6, 2024
62b3dde
Fix constants
YakDriver Nov 6, 2024
6ee6b1a
Update tools
YakDriver Nov 8, 2024
6c482c7
Change ephemeral documentation directory
YakDriver Nov 8, 2024
9d5d089
Merge f-ephemeral_resources_testing_logging
YakDriver Nov 19, 2024
0eb887d
Merge with origin/main
YakDriver Nov 20, 2024
3e264ab
Improve testing with the echo provider
YakDriver Nov 20, 2024
525193b
Fix type issue
YakDriver Nov 20, 2024
27579e6
Add notes about ephemeral
YakDriver Nov 20, 2024
9529d46
Merge remote-tracking branch 'origin/main' into f-ephemeral-lambda-in…
YakDriver Nov 20, 2024
09a3112
Add newness notice
YakDriver Nov 20, 2024
edfb088
Add changelog
YakDriver Nov 20, 2024
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/39988.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:new-ephemeral
aws_lambda_invocation
```
9 changes: 7 additions & 2 deletions internal/create/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,22 @@ const (
ErrActionCheckingExistence = "checking existence"
ErrActionCheckingNotRecreated = "checking not recreated"
ErrActionCheckingRecreated = "checking recreated"
ErrActionClosing = "closing"
ErrActionConfiguring = "configuring"
ErrActionCreating = "creating"
ErrActionDeleting = "deleting"
ErrActionExpandingResourceId = "expanding resource id"
ErrActionFlatteningResourceId = "flattening resource id"
ErrActionImporting = "importing"
ErrActionOpening = "opening"
ErrActionReading = "reading"
ErrActionRenewing = "renewing"
ErrActionSetting = "setting"
ErrActionUpdating = "updating"
ErrActionValidating = "validating"
ErrActionWaitingForCreation = "waiting for creation"
ErrActionWaitingForDeletion = "waiting for delete"
ErrActionWaitingForUpdate = "waiting for update"
ErrActionExpandingResourceId = "expanding resource id"
ErrActionFlatteningResourceId = "flattening resource id"
)

// ProblemStandardMessage is a standardized message for reporting errors and warnings
Expand Down
139 changes: 139 additions & 0 deletions internal/service/lambda/invocation_ephemeral.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package lambda

import (
"context"
"errors"

"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/ephemeral"
"github.com/hashicorp/terraform-plugin-framework/ephemeral/schema"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-provider-aws/internal/create"
"github.com/hashicorp/terraform-provider-aws/internal/framework"
"github.com/hashicorp/terraform-provider-aws/internal/framework/flex"
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"
)

// @EphemeralResource("aws_lambda_invocation", name="Invocation")
func newEphemeralInvocation(_ context.Context) (ephemeral.EphemeralResourceWithConfigure, error) {
return &ephemeralInvocation{}, nil
}

const (
ResNameInvocation = "Invocation"
)

type ephemeralInvocation struct {
framework.EphemeralResourceWithConfigure
}

func (e *ephemeralInvocation) Metadata(_ context.Context, _ ephemeral.MetadataRequest, response *ephemeral.MetadataResponse) {
response.TypeName = "aws_lambda_invocation"
}

func (e *ephemeralInvocation) Schema(ctx context.Context, _ ephemeral.SchemaRequest, response *ephemeral.SchemaResponse) {
response.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
"client_context": schema.StringAttribute{
Optional: true,
},
"executed_version": schema.StringAttribute{
Computed: true,
},
"function_error": schema.StringAttribute{
Computed: true,
},
"function_name": schema.StringAttribute{
Required: true,
},
"log_result": schema.StringAttribute{
Computed: true,
},
"log_type": schema.StringAttribute{
CustomType: fwtypes.StringEnumType[awstypes.LogType](),
Optional: true,
},
"payload": schema.StringAttribute{
Required: true,
Validators: []validator.String{
validators.JSON(),
},
},
"qualifier": schema.StringAttribute{
Optional: true,
},
"result": schema.StringAttribute{
Computed: true,
},
names.AttrStatusCode: schema.Int32Attribute{
Computed: true,
},
},
}
}

func (e *ephemeralInvocation) Open(ctx context.Context, req ephemeral.OpenRequest, resp *ephemeral.OpenResponse) {
conn := e.Meta().LambdaClient(ctx)
data := epInvocationData{}

resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}

input := &lambda.InvokeInput{
InvocationType: awstypes.InvocationTypeRequestResponse,
}
resp.Diagnostics.Append(flex.Expand(ctx, data, input)...)
if resp.Diagnostics.HasError() {
return
}

if input.FunctionName == nil {
data.Result = types.StringValue("")
resp.Diagnostics.Append(resp.Result.Set(ctx, &data)...)
return
}

output, err := conn.Invoke(ctx, input)
if err != nil {
resp.Diagnostics.AddError(
create.ProblemStandardMessage(names.Lambda, create.ErrActionOpening, ResNameInvocation, data.FunctionName.String(), err),
err.Error(),
)
return
}

if output.FunctionError != nil {
resp.Diagnostics.AddError(
create.ProblemStandardMessage(names.Lambda, create.ErrActionOpening, ResNameInvocation, data.FunctionName.String(), errors.New(aws.ToString(output.FunctionError))),
err.Error(),
)
return
}

resp.Diagnostics.Append(flex.Flatten(ctx, output, &data)...)
data.Result = flex.StringValueToFramework(ctx, string(output.Payload))
resp.Diagnostics.Append(resp.Result.Set(ctx, &data)...)
}

type epInvocationData struct {
ClientContext types.String `tfsdk:"client_context"`
ExecutedVersion types.String `tfsdk:"executed_version"`
FunctionError types.String `tfsdk:"function_error"`
FunctionName types.String `tfsdk:"function_name"`
LogResult types.String `tfsdk:"log_result"`
LogType fwtypes.StringEnum[awstypes.LogType] `tfsdk:"log_type"`
Payload types.String `tfsdk:"payload"`
Qualifier types.String `tfsdk:"qualifier"`
Result types.String `tfsdk:"result"`
StatusCode types.Int32 `tfsdk:"status_code"`
}
97 changes: 97 additions & 0 deletions internal/service/lambda/invocation_ephemeral_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0

package lambda_test

import (
"fmt"
"math/big"
"testing"

sdkacctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/knownvalue"
"github.com/hashicorp/terraform-plugin-testing/statecheck"
"github.com/hashicorp/terraform-plugin-testing/tfjsonpath"
"github.com/hashicorp/terraform-plugin-testing/tfversion"
"github.com/hashicorp/terraform-provider-aws/internal/acctest"
"github.com/hashicorp/terraform-provider-aws/names"
)

func TestAccLambdaInvocationEphemeral_basic(t *testing.T) {
ctx := acctest.Context(t)
rName := sdkacctest.RandomWithPrefix(acctest.ResourcePrefix)
echoResourceName := "echo.test"
dp := tfjsonpath.New("data")

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.PreCheck(ctx, t) },
ErrorCheck: acctest.ErrorCheck(t, names.LambdaServiceID),
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
tfversion.SkipBelow(tfversion.Version1_10_0),
},
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories,
ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories(ctx, acctest.ProviderNameEcho),
CheckDestroy: acctest.CheckDestroyNoop,
Steps: []resource.TestStep{
{
Config: testAccInvocationEphemeralConfig_basic(rName),
ConfigStateChecks: []statecheck.StateCheck{
statecheck.ExpectKnownValue(echoResourceName, dp.AtMapKey("executed_version"), knownvalue.StringExact("$LATEST")),
statecheck.ExpectKnownValue(echoResourceName, dp.AtMapKey("function_name"), knownvalue.NotNull()),
statecheck.ExpectKnownValue(echoResourceName, dp.AtMapKey("log_result"), knownvalue.Null()),
statecheck.ExpectKnownValue(echoResourceName, dp.AtMapKey("result"), knownvalue.StringExact(`{"key1":"value1","key2":"value2"}`)),
statecheck.ExpectKnownValue(echoResourceName, dp.AtMapKey(names.AttrStatusCode), knownvalue.NumberExact(big.NewFloat(200))),
},
},
},
})
}

func testAccInvocationEphemeralConfig_basic(rName string) string {
return acctest.ConfigCompose(
acctest.ConfigWithEchoProvider("ephemeral.aws_lambda_invocation.test"),
fmt.Sprintf(`
data "aws_partition" "current" {}

data "aws_iam_policy_document" "test" {
statement {
effect = "Allow"
actions = ["sts:AssumeRole"]
principals {
type = "Service"
identifiers = ["lambda.${data.aws_partition.current.dns_suffix}"]
}
}
}

resource "aws_iam_role" "test" {
name = %[1]q
assume_role_policy = data.aws_iam_policy_document.test.json
}

resource "aws_iam_role_policy_attachment" "test" {
policy_arn = "arn:${data.aws_partition.current.partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
role = aws_iam_role.test.name
}

resource "aws_lambda_function" "test" {
depends_on = [aws_iam_role_policy_attachment.test]

filename = "test-fixtures/lambda_invocation.zip"
function_name = %[1]q
role = aws_iam_role.test.arn
handler = "lambda_invocation.handler"
runtime = "nodejs18.x"
}

ephemeral "aws_lambda_invocation" "test" {
function_name = aws_lambda_function.test.arn

payload = jsonencode({
key1 = "value1"
key2 = "value2"
})
}
`, rName))
}
9 changes: 9 additions & 0 deletions internal/service/lambda/service_package_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions website/docs/ephemeral-resources/kms_secrets.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ description: |-

Decrypt multiple secrets from data encrypted with the AWS KMS service.

~> **NOTE:** Ephemeral resources are a new feature and may evolve as we continue to explore their most effective uses. [Learn more](https://developer.hashicorp.com/terraform/language/v1.10.x/resources/ephemeral).

## Example Usage

If you do not already have a `CiphertextBlob` from encrypting a KMS secret, you can use the below commands to obtain one using the [AWS CLI kms encrypt](https://docs.aws.amazon.com/cli/latest/reference/kms/encrypt.html) command. This requires you to have your AWS CLI setup correctly and replace the `--key-id` with your own. Alternatively you can use `--plaintext 'master-password'` (CLIv1) or `--plaintext fileb://<(echo -n 'master-password')` (CLIv2) instead of reading from a file.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
subcategory: "Lambda"
layout: "aws"
page_title: "AWS: aws_lambda_invocation"
description: |-
Invoke AWS Lambda Function
---

# Ephemeral: aws_lambda_invocation

Use this ephemeral resource to invoke a Lambda function. The lambda function is invoked with the [RequestResponse](https://docs.aws.amazon.com/lambda/latest/dg/API_Invoke.html#API_Invoke_RequestSyntax) invocation type.

~> **NOTE:** Ephemeral resources are a new feature and may evolve as we continue to explore their most effective uses. [Learn more](https://developer.hashicorp.com/terraform/language/v1.10.x/resources/ephemeral).

~> **NOTE:** The `aws_lambda_invocation` ephemeral resource invokes the function during every `plan` and `apply` when the function is known. A common use case for this functionality is when invoking a lightweight function—where repeated invocations are acceptable—that produces sensitive information you do not want to store in the state.

~> **NOTE:** If you get a `KMSAccessDeniedException: Lambda was unable to decrypt the environment variables because KMS access was denied` error when invoking an [`aws_lambda_function`](/docs/providers/aws/r/lambda_function.html) with environment variables, the IAM role associated with the function may have been deleted and recreated _after_ the function was created. You can fix the problem two ways: 1) updating the function's role to another role and then updating it back again to the recreated role, or 2) by using Terraform to `taint` the function and `apply` your configuration again to recreate the function. (When you create a function, Lambda grants permissions on the KMS key to the function's IAM role. If the IAM role is recreated, the grant is no longer valid. Changing the function's role or recreating the function causes Lambda to update the grant.)

## Example Usage

### Basic Example

```terraform
ephemeral "aws_lambda_invocation" "example" {
function_name = aws_lambda_function.lambda_function_test.function_name

payload = jsonencode({
key1 = "value1"
key2 = "value2"
})
}

output "result_entry" {
value = jsondecode(aws_lambda_invocation.example.result)["key1"]
ephemeral = true
}
```

## Argument Reference

The following arguments are required:

* `function_name` - (Required) Name or ARN of the Lambda function, version, or alias. You can append a version number or alias. If you specify only the function name, it is limited to 64 characters in length.
* `payload` - (Required) JSON that you want to provide to your Lambda function as input.

The following arguments are optional:

* `client_context` - (Optional) Up to 3583 bytes of base64-encoded data about the invoking client to pass to the function in the context object.
* `log_type` - (Optional) Set to `Tail` to include the execution log in the response. Valid values are `None` and `Tail`.
* `qualifier` - (Optional) Version or alias to invoke a published version of the function. Defaults to `$LATEST`.

## Attribute Reference

This resource exports the following attributes in addition to the arguments above:

* `executed_version` - Version of the function that executed. When you invoke a function with an alias, the version the alias resolved to.
* `function_error` - If present, indicates that an error occurred during function execution. Details about the error are included in `result`.
* `log_result` - Last 4 KB of the execution log, which is base64-encoded.
* `result` - String result of the lambda function invocation.
* `status_code` - HTTP status code is in the 200 range for a successful request.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ description: |-

Retrieve information about a Secrets Manager secret version, including its secret value. To retrieve secret metadata, see the [`aws_secretsmanager_secret` data source](/docs/providers/aws/d/secretsmanager_secret.html).

~> **NOTE:** Ephemeral resources are a new feature and may evolve as we continue to explore their most effective uses. [Learn more](https://developer.hashicorp.com/terraform/language/v1.10.x/resources/ephemeral).

## Example Usage

### Retrieve Current Secret Version
Expand Down
Loading