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

New Resource: aws_custom_resource #10096

Closed

Conversation

scottwinkler
Copy link
Contributor

@scottwinkler scottwinkler commented Sep 12, 2019

Supersedes #3361.

Introduction

Lambda-backed Custom resources are indispensable for bridging the gap of infrastructure not currently supported by Terraform, without having to go through the effort of writing a custom plugin or extending existing providers. Previously, custom resources are supported through CloudFormation but not Terraform, so the old workaround was to call a CloudFormation script from Terraform thats calls the custom resource. This code provides an adapter for the Custom Resource making it a first class Terraform managed resource.

How to Use

Argument Reference

The following arguments are supported:

  • service_token - (Required) The service token (an Amazon SNS topic or AWS Lambda function Amazon Resource Name) that is obtained from the custom resource provider to access the service.
  • resource_type - (Required) The developer-chosen resource type of the custom resource
  • resource_properties - (Optional) This field contains the contents of the Properties object sent by the Terraform. Its contents are defined by the custom resource provider.

Attribute Reference

In addition to all arguments above, the following attributes are exported:

  • id - A random id that is unique to this resource.
  • old_resource_properties - Used only for Update requests. Contains the resource properties that were declared previous to the update request.
  • data - The custom resource provider-defined name-value pairs sent with the response.

This is an example of how the quick certificate could be used:

resource "aws_iam_role" "iam_for_lambda" {
  name = "iam_for_lambda"
  force_detach_policies = true
  assume_role_policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": "sts:AssumeRole",
      "Principal": {
        "Service": "lambda.amazonaws.com"
      },
      "Effect": "Allow",
      "Sid": ""
    }
  ]
}
EOF
}

resource "aws_iam_policy" "lambda_logging" {
  name = "lambda_logging"
  path = "/"
  description = "IAM policy for logging from a lambda"

  policy = <<EOF
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": [
        "logs:CreateLogStream",
        "logs:PutLogEvents"
      ],
      "Resource": "arn:aws:logs:*:*:*",
      "Effect": "Allow"
    }
  ]
}
EOF
}

resource "aws_iam_role_policy_attachment" "lambda_logs" {
  role = aws_iam_role.iam_for_lambda.name
  policy_arn = aws_iam_policy.lambda_logging.arn
}

resource "aws_lambda_function" "lambda_function" {
  filename      = "${path.module}/test-fixtures/custom_resource.zip"
  function_name = "custom_resource_lambda"
  role          = aws_iam_role.iam_for_lambda.arn
  handler       = "index.handler"
  runtime       = "nodejs10.x"
}

resource "aws_custom_resource" "custom_resource" {
  service_token = aws_lambda_function.lambda_function.arn
  resource_type = "CustomTest"
  resource_properties = {
    a = "1"
    b = "2"
  }
}

When you run "terraform apply", your lambda function will be invoked with a JSON event as follows. RequestType will be one of Create||Update||Delete, and it is up to you to decide how to handle that. In this case, it is "Create". In addition, you will receive the ResourceType, which is defined by you, and can be used to have a single lambda function responsible for handling the creation of multiple custom resources. Finally, you will receive the ResourceProperties which is a map of input parameters.

{
    "RequestType": "Create",
    "ResourceType": "CustomTest",
    "OldResourceProperties": {
        "a": "1",
        "b": "1"
    },
    "ResourceProperties": {
        "a": "1",
        "b": "1"
    }
}

Similarly, if you change the resource properties and run terraform apply, your Lambda function will get called with an "Update" event that contains information about the OldResourceProperties, as well as the new ResourceProperties. In this example, b was changed from 1 to 2, and this is the event that was generated

{
    "RequestType": "Update",
    "ResourceType": "CustomTest",
    "OldResourceProperties": {
        "a": "1",
        "b": "1"
    },
    "ResourceProperties": {
        "a": "1",
        "b": "2"
    }
}

Delete is the final event that is created, and is run on "terraform destroy". It is up to you to figure out how to clean up the resources you created. Below is an example of a "Delete" event.

{
    "RequestType": "Delete",
    "ResourceType": "CustomTest",
    "OldResourceProperties": {
        "a": "1",
        "b": "2"
    },
    "ResourceProperties": {
        "a": "1",
        "b": "2"
    }
}

In your lambda function, you are expected to return a response to the with a JSON object like follows. Status is required and must be either "SUCCESS" or "FAILURE". If "FAILURE", then a Reason must be specified, which will be printed in the terraform console as an error message. Finally, Data is a map[string]string that will be saved and can be used as an output from the module. i.e aws_custom_resource.test.data[“out1”].

{  "Status" : "SUCCESS",  "Reason" : "",  "Data" : {       "out1" : "val1",       "out2" : "val2"  }}

Below is a trivial example of the source code for a lambda function that handles the customresource.

const validRequestTypes = ["Create", "Update", "Delete"]

exports.handler = async (event) => {
    console.log("EVENT: \n" + JSON.stringify(event, null, 2))
    let requestType = event.RequestType
    let valid = validRequestTypes.includes(requestType)
    let status = "SUCCESS"
    let reason = "N/A"
    let data = {}
    if (valid) {
        console.log("Performing operation: " + requestType)
        for (const [key, value] of Object.entries(event.ResourceProperties)) {
            data[key] = String(value)
        }
    }
    else {
        status = "FAILURE"
        reason = "Invalid request type."
    }

    const response = {
        Status: status,
        Reason: reason,
        Data: data,
    }

    return response
}

Output from acceptance testing:

$ make testacc TEST=./aws TESTARGS='-run=TestAccAWSCustomResource_basic'
==> Checking that code complies with gofmt requirements...
TF_ACC=1 go test ./aws -v -parallel 20 -run=TestAccAWSCustomResource_basic -timeout 120m
=== RUN   TestAccAWSCustomResource_basic
=== PAUSE TestAccAWSCustomResource_basic
=== CONT  TestAccAWSCustomResource_basic
--- PASS: TestAccAWSCustomResource_basic (36.30s)
PASS
ok  	github.com/terraform-providers/terraform-provider-aws/aws	37.583s
...

@scottwinkler scottwinkler requested a review from a team September 12, 2019 22:59
@ghost ghost added size/XL Managed by automation to categorize the size of a PR. provider Pertains to the provider itself, rather than any interaction with AWS. tests PRs: expanded test coverage. Issues: expanded coverage, enhancements to test infrastructure. documentation Introduces or discusses updates to documentation. labels Sep 12, 2019
Base automatically changed from master to main January 23, 2021 00:56
@breathingdust breathingdust requested a review from a team as a code owner January 23, 2021 00:56
@zhelding
Copy link
Contributor

Pull request #21306 has significantly refactored the AWS Provider codebase. As a result, most PRs opened prior to the refactor now have merge conflicts that must be resolved before proceeding.

Specifically, PR #21306 relocated the code for all AWS resources and data sources from a single aws directory to a large number of separate directories in internal/service, each corresponding to a particular AWS service. This separation of code has also allowed for us to simplify the names of underlying functions -- while still avoiding namespace collisions.

We recognize that many pull requests have been open for some time without yet being addressed by our maintainers. Therefore, we want to make it clear that resolving these conflicts in no way affects the prioritization of a particular pull request. Once a pull request has been prioritized for review, the necessary changes will be made by a maintainer -- either directly or in collaboration with the pull request author.

For a more complete description of this refactor, including examples of how old filepaths and function names correspond to their new counterparts: please refer to issue #20000.

For a quick guide on how to amend your pull request to resolve the merge conflicts resulting from this refactor and bring it in line with our new code patterns: please refer to our Service Package Refactor Pull Request Guide.

Copy link

Marking this pull request as stale due to inactivity. This helps our maintainers find and focus on the active pull requests. If this pull request receives no comments in the next 30 days it will automatically be closed. Maintainers can also remove the stale label.

If this pull request was automatically closed and you feel this pull request should be reopened, we encourage creating a new pull request linking back to this one for added context. Thank you!

@github-actions github-actions bot added the stale Old or inactive issues managed by automation, if no further action taken these will get closed. label Dec 15, 2024
@github-actions github-actions bot closed this Jan 18, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Introduces or discusses updates to documentation. provider Pertains to the provider itself, rather than any interaction with AWS. size/XL Managed by automation to categorize the size of a PR. stale Old or inactive issues managed by automation, if no further action taken these will get closed. tests PRs: expanded test coverage. Issues: expanded coverage, enhancements to test infrastructure.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants