Terraform
Invoke actions with Terraform
When you manage an infrastructure project with Terraform, you define your infrastructure in configuration files and then use Terraform to plan and apply changes to your infrastructure. Terraform's declarative approach to infrastructure management lets you consistently and repeatably manage infrastructure. However, some infrastructure tasks consist of performing actions on existing infrastructure objects rather than creating, updating, and destroying them. Examples of these actions include restarting a server, rotating a database password, and triggering a function.
Like resources, data sources, and functions, each Terraform provider can support a set of related actions. When you write your Terraform configuration, you can include action blocks and triggers to invoke these actions either manually or with a trigger when you apply your configuration.
In this tutorial, you will deploy a set of example infrastructure, and then add an action, which you will then invoke from the command line as well as through a trigger when you create or update a related resource.
Prerequisites
This tutorial assumes that you are familiar with the Terraform workflow. If you are new to Terraform, complete the Get Started collection first.
In order to complete this tutorial, you will need the following:
- The Terraform CLI (v1.14.0+) installed.
- An AWS account and associated credentials that allow you to create AWS resources including an EC2 instance, VPC, and security groups.
- The AWS CLI installed.
- The
jqandcurlcommand line tools installed.
Clone example repository
In your terminal, clone the example repository for this tutorial. This repository contains example configuration that you will use to provision infrastructure to run actions on.
$ git clone https://github.com/hashicorp-education/learn-terraform-actions
Provision example workspace
Change to the repository directory.
$ cd learn-terraform-actions/aws
Now run terraform init to initialize your Terraform workspace.
$ terraform init
Initializing the backend...
Initializing modules...
Initializing provider plugins...
- Reusing previous version of hashicorp/archive from the dependency lock file
- Reusing previous version of hashicorp/random from the dependency lock file
- Reusing previous version of hashicorp/aws from the dependency lock file
- Using previously-installed hashicorp/archive v2.7.1
- Using previously-installed hashicorp/random v3.7.2
- Using previously-installed hashicorp/aws v6.25.0
Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
Terraform initialized your workspace and downloaded the required providers for your configuration.
Review example configuration
Review the example configuration in main.tf.
aws/main.tf
## ...
resource "aws_sqs_queue" "job_queue" {
name = "${var.project_name}-queue"
}
## ...
resource "aws_lambda_function" "api_handler" {
function_name = "${var.project_name}-handler"
runtime = "python3.14"
handler = "index.handler"
role = aws_iam_role.lambda_role.arn
s3_bucket = aws_s3_bucket.lambda_bucket.id
s3_key = aws_s3_object.lambda_object.key
source_code_hash = data.archive_file.lambda_archive.output_base64sha256
environment {
variables = {
QUEUE_URL = aws_sqs_queue.job_queue.url
}
}
}
## ...
module "http_api" {
source = "terraform-aws-modules/apigateway-v2/aws"
version = "~> 6.0.0"
name = "${var.project_name}-http-api"
description = "HTTP API for lambda function"
protocol_type = "HTTP"
create_domain_name = false
cors_configuration = {
allow_origins = ["*"]
allow_methods = ["POST"]
allow_headers = ["content-type"]
}
routes = {
"POST /job" = {
integration = {
uri = aws_lambda_function.api_handler.arn
type = "AWS_PROXY"
payload_format_version = "2.0"
timeout_milliseconds = 10000
}
}
}
}
This configuration defines an AWS SQS queue, Lambda function, and API Gateway,
along with other related resources. The code for the lambda function is stored
in the lambda subdirectory. When triggered, the function publishes a
message to the SQS queue. The API Gateway configuration lets you call the
Lambda function over HTTP.
Create infrastructure
Next, apply your configuration. Respond to the confirmation prompt with a yes
to create the example infrastructure for this tutorial.
$ terraform apply
data.archive_file.lambda_archive: Reading...
data.archive_file.lambda_archive: Read complete after 0s [id=fa24aa9565e876be12ede390141389bf2c8e31e0]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# aws_iam_role.lambda_role will be created
+ resource "aws_iam_role" "lambda_role" {
+ arn = (known after apply)
+ assume_role_policy = jsonencode(
## ...
Plan: 15 to add, 0 to change, 0 to destroy.
Changes to Outputs:
+ aws_region = "us-west-1"
+ http_api_url = (known after apply)
+ lambda_function_name = "learn-actions-handler"
+ queue_url = (known after apply)
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
random_pet.lambda_bucket_name: Creating...
random_pet.lambda_bucket_name: Creation complete after 0s [id=learn-terraform-functions-morally-basically-complete-hound]
module.http_api.aws_cloudwatch_log_group.this["this"]: Creating...
aws_sqs_queue.job_queue: Creating...
## ...
module.http_api.aws_apigatewayv2_stage.this[0]: Creating...
module.http_api.aws_apigatewayv2_stage.this[0]: Creation complete after 0s [id=$default]
Apply complete! Resources: 15 added, 0 changed, 0 destroyed.
Outputs:
aws_region = "us-west-1"
http_api_url = "https://c4xow1wvef.execute-api.us-west-1.amazonaws.com/job"
lambda_function_name = "learn-actions-handler"
queue_url = "https://sqs.us-west-1.amazonaws.com/949008909725/learn-actions-queue"
Review the resources created by your workspace.
Verify your infrastructure
Verify that your Lambda function and SQS queue work as expected.
$ curl --request POST \
--header "Content-Type: application/json" \
--data '{"message": "Invoke lambda from curl", "type": "CLI"}' \
"$(terraform output -raw http_api_url)"
The curl command sends a POST request with the given message to the HTTP endpoint for the
Lambda function provided by the API Gateway. The Lambda function
then publishes a message to your SQS queue. Use the AWS CLI to retrieve messages
from the queue. Pipe the response through jq to format the JSON output.
$ aws sqs receive-message --region "$(terraform output -raw aws_region)" \
--queue-url "$(terraform output -raw queue_url)" \
--max-number-of-messages 10 \
--visibility-timeout 0 \
--wait-time-seconds 5 | jq .
{
"Messages": [
{
"MessageId": "c7158ae2-14b0-4be8-bae3-8e33292b13aa",
"ReceiptHandle": "AQEB9Z3zxLMBr2FnXBFmqTjOTy5VqQilsZbz2CBowdYI7RuWA0ayvJ/HF0n8y5AG5VBDZJXkWb8dLNq7ez1Acp+85cKdB7Oa9ZQcDo4WKtKAobZheic74jEY5gqyMlePl/KDPQo6tt6xGZ0TRDl7TXVEXJmTi+xIVgpG9xpxz2dWlP+MoKg75HM0Gyi3+hObOhS3JiJEOE0O/g+hJlGryM2tnAOmsiFiUawm5Nvkvs637U86AU8ZaNJd+PFAdZGOeXQ8CpWAlqrr07ipSnlpiLa6GdQ0AKXh6yfULzHygU+qFc9RUMgArzDqxa7jYKdAdAmNRWQUH36aMF3ry/OAeCkjhBNkUWcVt92PXHI/PuP+9Lglm6phCXBE1gdgKvojDLZQepprhCshEoAJgg1bG8tkjw==",
"MD5OfBody": "008eb2f05efe0a4fcb8e3e5874d5a677",
"Body": "Invoke lambda from curl"
}
]
}
The AWS CLI prints out the contents of the message received from your Lambda function.
In the next step, you will add and invoke an action to your infrastructure.
Add an action
Add an action to your configuration. Like resources, data sources, and functions, each provider can define any number of actions relevant to the infrastructure it supports.
aws/main.tf
action "aws_lambda_invoke" "api_handler" {
config {
function_name = aws_lambda_function.api_handler.function_name
payload = jsonencode({
message = "Invoke lambda from action",
type = "test"
})
}
}
When you invoke this action, it calls the corresponding AWS Lambda function with the specified payload. You can review the documentation for this action type in the AWS Provider documentation in the Terraform registry.
Invoke your action
Once you've added an action to your workspace's configuration, you can invoke it
from the command line by specifying the action with the -invoke argument to
plan and apply operations. Refer to actions with the format
action.<TYPE>.<LABEL>. Invoke your action now.
$ terraform apply -invoke=action.aws_lambda_invoke.api_handler
random_pet.lambda_bucket_name: Refreshing state... [id=learn-terraform-functions-morally-basically-complete-hound]
data.archive_file.lambda_archive: Reading...
data.archive_file.lambda_archive: Read complete after 0s [id=fa24aa9565e876be12ede390141389bf2c8e31e0]
## ...
Terraform will perform the following actions:
Plan: 0 to add, 0 to change, 0 to destroy. Actions: 1 to invoke.
Terraform will invoke the following action(s):
# action.aws_lambda_invoke.api_handler will be invoked
action "aws_lambda_invoke" "api_handler" {
config {
function_name = "learn-actions-handler"
payload = jsonencode(
{
message = "Invoke lambda from action"
type = "test"
}
)
}
}
Would you like to invoke the specified actions?
Terraform will invoke the actions described above, and any changes will be written to the state without modifying real infrastructure
There is no undo. Only 'yes' will be accepted to confirm.
Enter a value: yes
Action started: action.aws_lambda_invoke.api_handler (triggered by CLI)
Action action.aws_lambda_invoke.api_handler (triggered by CLI): Invoking Lambda function learn-actions-handler...
Action action.aws_lambda_invoke.api_handler (triggered by CLI): Lambda function learn-actions-handler invoked successfully (status: 200, payload: 93 bytes)
Action complete: action.aws_lambda_invoke.api_handler (triggered by CLI)
Apply complete! Resources: 0 added, 0 changed, 0 destroyed. Actions: 1 invoked.
Outputs:
aws_region = "us-west-1"
http_api_url = "https://c4xow1wvef.execute-api.us-west-1.amazonaws.com/job"
lambda_function_name = "learn-actions-handler"
queue_url = "https://sqs.us-west-1.amazonaws.com/949008909725/learn-actions-queue"
Respond to the confirmation prompt with a yes to invoke the action.
Just as when you invoked the action via the API Gateway, the Lambda function publishs a message to the SQS queue. Retrieve the messages to verify that the function executed correctly.
$ aws sqs receive-message --region "$(terraform output -raw aws_region)" \
--queue-url "$(terraform output -raw queue_url)" \
--max-number-of-messages 10 \
--visibility-timeout 0 \
--wait-time-seconds 5 | jq .
{
"Messages": [
## ...
{
"MessageId": "977b1f2f-6ecb-430e-b453-5bb6b345b29b",
"ReceiptHandle": "AQEB578VbE9yKlh4D1xm16qo66zKKlJs3n7sGQtfa64wHAYIPOd4AXn2rwfOUKoL+B2dBPgrqEjKKMnlPT3wBho0qEKtFE70+QNv4UOWBFV9Sy/nD4RhNmS89s30H4HVoCJbRIjrgKXpUV3VEdDNksJkB+eqR03SaRGO/OmvQmP3oaiiNUKx/FlysbwYIPJzNClPQYfQ+KYyIhYG3AQFb/XVvf6qldikDKX6kirLKiFviLCpkpYhfGnnmWVSnuiFdlvGBNDQCOyCGhAc8Y5Bm4e4o1io60+vRdMs4qJAeKGSjHfqdpIfMvpL7Erkg+XpxRNKR+o/1HpyhEBeSx3lSaKd/hV39kmj3i1mEhrI7Xlm13et+cRdGGYVaRw8aTq0wjLJAwGQBbnmGy6Hb/BlLC8cfw==",
"MD5OfBody": "c4cbfa11b50862985b830bc9e72b5472",
"Body": "Invoke lambda from action"
}
]
}
Add a trigger for your action
In addition to manually invoking actions via the CLI, you can add lifecycle triggers to invoke your action either before or after a given resources is created, updated, or destroyed.
Update the configuration for your Lambda function to invoke your action whenever Terraform creates or updates the function.
aws/main.tf
resource "aws_lambda_function" "api_handler" {
function_name = "${var.project_name}-handler"
runtime = "python3.14"
handler = "index.handler"
role = aws_iam_role.lambda_role.arn
s3_bucket = aws_s3_bucket.lambda_bucket.id
s3_key = aws_s3_object.lambda_object.key
source_code_hash = data.archive_file.lambda_archive.output_base64sha256
environment {
variables = {
QUEUE_URL = aws_sqs_queue.job_queue.url
}
}
lifecycle {
action_trigger {
events = [after_create, after_update]
actions = [action.aws_lambda_invoke.api_handler]
}
}
}
To trigger an update of your Lambda function, modify the code in
aws/lambda/index.py to change the default value for the event type.
aws/lambda/index.py
def handler(event, context):
# When called via API Gateway, the payload is in event["body"]. Otherwise,
# the event itself is the payload.
payload = json.loads(event.get("body", "{}")) or event
message = payload.get("message", "default message")
event_type = payload.get("type", "DEFAULT")
## ...
Apply your configuration to update your Lambda function and have Terraform
invoke your action after it updates the function. Respond to the confirmation
prompt with a yes.
$ terraform apply
random_pet.lambda_bucket_name: Refreshing state... [id=learn-terraform-functions-morally-basically-complete-hound]
data.archive_file.lambda_archive: Reading...
data.archive_file.lambda_archive: Read complete after 0s [id=53a541da739d98cd85b913e03b4f33f30c9a8168]
module.http_api.aws_cloudwatch_log_group.this["this"]: Refreshing state... [id=/aws/apigateway/learn-actions-http-api/default]
## ...
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
~ update in-place
Terraform will perform the following actions:
# aws_lambda_function.api_handler will be updated in-place
~ resource "aws_lambda_function" "api_handler" {
id = "learn-actions-handler"
## ...
Plan: 0 to add, 2 to change, 0 to destroy. Actions: 1 to invoke.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
aws_s3_object.lambda_object: Modifying... [id=learn-terraform-functions-morally-basically-complete-hound/lambda.zip]
aws_s3_object.lambda_object: Modifications complete after 0s [id=learn-terraform-functions-morally-basically-complete-hound/lambda.zip]
aws_lambda_function.api_handler: Modifying... [id=learn-actions-handler]
aws_lambda_function.api_handler: Modifications complete after 6s [id=learn-actions-handler]
Action started: action.aws_lambda_invoke.api_handler (triggered by aws_lambda_function.api_handler)
Action action.aws_lambda_invoke.api_handler (triggered by aws_lambda_function.api_handler): Invoking Lambda function learn-actions-handler...
Action action.aws_lambda_invoke.api_handler (triggered by aws_lambda_function.api_handler): Lambda function learn-actions-handler invoked successfully (status: 200, payload: 93 bytes)
Action complete: action.aws_lambda_invoke.api_handler (triggered by aws_lambda_function.api_handler)
Apply complete! Resources: 0 added, 2 changed, 0 destroyed. Actions: 1 invoked.
Outputs:
aws_region = "us-west-1"
http_api_url = "https://c4xow1wvef.execute-api.us-west-1.amazonaws.com/job"
lambda_function_name = "learn-actions-handler"
queue_url = "https://sqs.us-west-1.amazonaws.com/949008909725/learn-actions-queue"
Terraform updated your lambda function and then triggered your action.
Retrieve the messages from your SQS queue to verify that the function executed correctly.
$ aws sqs receive-message --region "$(terraform output -raw aws_region)" \
--queue-url "$(terraform output -raw queue_url)" \
--max-number-of-messages 10 \
--visibility-timeout 0 \
--wait-time-seconds 5 | jq .
{
"Messages": [
## ...
{
"MessageId": "c7158ae2-14b0-4be8-bae3-8e33292b13aa",
"ReceiptHandle": "AQEBFwOceRd9llYIzTqiq260VJk3AZXL/R2DfS/ycpXwC/xIc9nXw0fiQ09U/BnzUybhV+T9Km4VFBlxzpr8nvz88kMuUYhp3CtTu1Sp5DzpOAGmwfcnCSYIf9iEH0epCZzJ1k4CK66f3/SBSquLeteHVnyvI9x/RFkEQDw63JnwYzr4+QcnZ6/npPEtkRLrP5tpMidE10zAGDMVbHodZ8zeiFtVeICekeiV6cBgu7ilEchcoOM49pKzedDqpDPpDWXmWhBm8T2A7j5W/HkSvnsN9tjKSMmnN+dEoH2ARM//Q7Pb9eoKdVHRnlminwsgTvRHkfh6u8o2qEEsL3JMwh8AkWYakT4Geql7dR2d/vfOtlTurwXUdmlbkVOZjdWaAIWBQW9AEEaGHRyNAyy5C6dlew==",
"MD5OfBody": "008eb2f05efe0a4fcb8e3e5874d5a677",
"Body": "Invoke lambda from action"
}
]
}
Destroy infrastructure
You have now implemented an action in your Terraform configuration, and invoked it via the CLI and a trigger.
Destroy the infrastructure you created for this tutorial by running terraform
destroy. Respond yes to the prompt to confirm the operation.
$ terraform destroy
random_pet.lambda_bucket_name: Refreshing state... [id=learn-terraform-functions-morally-basically-complete-hound]
data.archive_file.lambda_archive: Reading...
data.archive_file.lambda_archive: Read complete after 0s [id=53a541da739d98cd85b913e03b4f33f30c9a8168]
## ...
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
- destroy
Terraform will perform the following actions:
# aws_iam_role.lambda_role will be destroyed
- resource "aws_iam_role" "lambda_role" {
- arn = "arn:aws:iam::949008909725:role/learn-actions-lambda-role" -> null
- assume_role_policy = jsonencode(
## ...
Plan: 0 to add, 0 to change, 15 to destroy.
Changes to Outputs:
- aws_region = "us-west-1" -> null
- http_api_url = "https://c4xow1wvef.execute-api.us-west-1.amazonaws.com/job" -> null
- lambda_function_name = "learn-actions-handler" -> null
- queue_url = "https://sqs.us-west-1.amazonaws.com/949008909725/learn-actions-queue" -> null
Do you really want to destroy all resources?
Terraform will destroy all your managed infrastructure, as shown above.
There is no undo. Only 'yes' will be accepted to confirm.
Enter a value: yes
aws_iam_role_policy.lambda_policy: Destroying... [id=learn-actions-lambda-role:learn-actions-lambda-policy]
aws_lambda_permission.allow_http_api: Destroying... [id=AllowHTTPAPIInvoke]
aws_s3_bucket_acl.lambda_bucket: Destroying... [id=learn-terraform-functions-morally-basically-complete-hound,private]
## ...
random_pet.lambda_bucket_name: Destroying... [id=learn-terraform-functions-morally-basically-complete-hound]
random_pet.lambda_bucket_name: Destruction complete after 0s
aws_sqs_queue.job_queue: Destruction complete after 2s
Destroy complete! Resources: 15 destroyed.
Next steps
In this tutorial, you configured an action in your Terraform workspace. You then invoked the action manually via the CLI, and added a trigger to have Terraform automatically invoke the action whenever it updates or creates the specified resource.
Review the following resources to learn more about how to use Terraform actions to perform operations on your infrastructure:
- Review the Terraform actions documentation.
- Read our Terraform actions blog post.
- Learn how to implement actions in your own Terraform provider.