Defining Custom Actions

Step 1: Define the Action Resource

First, define a resource facets_action block. This block serves as the container for your entire Action definition. Give it a unique local name (e.g., db_snapshot) and a human-readable name that will be displayed in the Facets UI.

The target_resource_type argument is crucial; it links the Action to a specific module (e.g., aws-rds), ensuring it only appears on resources of that type.

In actions.tf:

resource "facets_action" "db_snapshot" {
  name                 = "Create Database Snapshot"
  target_resource_type = "aws-rds"

  #... further configuration will be added in the next steps
}

Step 2: Add a Clear Description

Write a concise, one-sentence description that clearly explains what the Action does. This description appears in the UI and helps users understand the purpose of the Action before running it.

resource "facets_action" "db_snapshot" {
  name                 = "Create Database Snapshot"
  description          = "Creates a manual snapshot of the RDS database instance."
  target_resource_type = "aws-rds"

  #...
}

Step 3: Define User Inputs

If your Action requires parameters from the user at runtime, define them in an inputs block. This block uses standard HCL variable syntax and will render as a form in the Facets UI when a user runs the Action.

You can specify the type, description, a default value, and whether the input is required.

resource "facets_action" "db_snapshot" {
  #... name, description, target_resource_type
  
  inputs {
    variable "snapshot_identifier" {
      type        = string
      description = "A unique name for the database snapshot."
      required    = true
    }
    variable "is_replicated" {
      type        = bool
      description = "Replicate this snapshot to a DR region."
      default     = false
    }
  }

  #...
}

Note on Standard Inputs: Facets automatically provides context about the target resource. You can access details like instance_name and environment within the Terraform phase using the data "facets_resource" "target" {} data source.


Step 4: Create the Execution Steps

The core logic of an Action is defined in two phases:

  1. Terraform phase (terraform_config) — gathers context.
  2. Container phase (container_config) — executes the task.

Phase 1: Gather Context with terraform_config

The terraform_config block contains an ephemeral Terraform script that runs to gather necessary information from your cloud environment.

resource "facets_action" "db_snapshot" {
  #... name, description, target_resource_type, inputs

  terraform_config = <<-EOT
    # Access the target resource's attributes
    data "facets_resource" "target" {}

    # Expose the database instance ID as an output
    output "db_instance_identifier" {
      value = data.facets_resource.target.attributes.identifier
    }
  EOT

  #... container_config to follow
}

Phase 2: Execute the Task with container_config

The container_config block defines the containerized step that performs the actual work.

  • Pick a container image:

    • AWS tasks → amazon/aws-cli:latest
    • Kubernetes tasks → bitnami/kubectl:latest
    • General scripting → alpine:latest
    • Database tasks → redis:latest, postgres:latest
  • Define environment variables: Use the env map to pass data from Terraform outputs and user inputs.

  • Add a script: Use command and args to define the execution logic.

resource "facets_action" "db_snapshot" {
  #... name, description, target_resource_type, inputs, terraform_config

  container_config {
    image   = "amazon/aws-cli:latest"
    command = ["/bin/sh", "-c"]
    args    = [
      <<-EOT
        set -eou pipefail
        echo "Creating snapshot for database instance: $DB_ID"
        aws rds create-db-snapshot           --db-instance-identifier "$DB_ID"           --db-snapshot-identifier "$SNAPSHOT_ID"
        echo "Snapshot creation initiated successfully."
      EOT
    ]

    env = {
      "DB_ID"       = terraform.outputs.db_instance_identifier
      "SNAPSHOT_ID" = var.snapshot_identifier
    }
  }
}

Step 5: Test the Script Logic Manually

Before embedding a complex script into the actions.tf file, test it locally with Docker.

# Example of local testing using Docker
docker run --rm -it   -e DB_ID="my-test-db"   -e SNAPSHOT_ID="my-test-snapshot-local"   -e AWS_ACCESS_KEY_ID="YOUR_KEY"   -e AWS_SECRET_ACCESS_KEY="YOUR_SECRET"   -e AWS_REGION="us-east-1"   amazon/aws-cli:latest /bin/sh -c '
    set -eou pipefail
    echo "Testing snapshot for database instance: $DB_ID"
    aws rds create-db-snapshot       --db-instance-identifier "$DB_ID"       --db-snapshot-identifier "$SNAPSHOT_ID"
    echo "Test successful."
  '

This local feedback loop is much faster than committing and running the Action through the UI for every change.


Step 6: Make the Action Reusable

Design your Actions for reusability across different environments and resources.

  • Use input parameters: Parameterize values like names, flags, or patterns.
  • Avoid hardcoding: Always fetch values dynamically in the terraform_config phase.

By following these principles, you can build a library of robust, reusable automation components that scale with your organization.