linuxlab.io
Tutorials▾
  • Linux & networking
    File system, processes, TCP/IP, BGP and OSPF
    →
  • Terraform & IaC
    HCL, state, plan/apply on a LocalStack sandbox
    →
  • Git & GitHub
    Object model, plumbing, branching, GitHub Actions
    →
All tutorials →
PricingAboutSign inCreate account
/
Intro
Lessons
Footer
linuxlab-TutorialsPricingAboutPrivacy & cookies
Copyright © 2026 LinuxLab. All rights reserved.
linuxlab.io
Tutorials▾
  • Linux & networking
    File system, processes, TCP/IP, BGP and OSPF
    →
  • Terraform & IaC
    HCL, state, plan/apply on a LocalStack sandbox
    →
  • Git & GitHub
    Object model, plumbing, branching, GitHub Actions
    →
All tutorials →
PricingAboutSign inCreate account
/
  • Introduction
  • Lessons
  • How it works
  • Knowledge base
  • Cheat sheet
  • Capstone
  • Interview prep
home/terraform/lessons/tf-intermediate-11-utility-providers

lesson ── terraform-intermediate ── ~16 мин ── 5 шагов

Utility providers: random, time, archive, external

Four utility providers that real-world HCL keeps reaching for: random (unique names and passwords), time (timestamps and delays), archive (packaging lambda code), external (calling any script). In this lesson you use all four in one project. This is the usual mix of reusable building blocks.

▶ интерактивный sandbox

Поднимется пара контейнеров: terraform 1.9 и localstack 3.8 в одной сети. В браузере откроется терминал, можно сразу terraform init. Каждый шаг проверяется автоматически. TTL 45 минут, без регистрации.

запустить sandbox →

stack ── terraform · localstack · 1 GB RAM · самоуничтожается через 45 мин простоя

Шаги

  1. 01

    Drop in a minimal Lambda and package it

    bash
    cd /home/student/tf-utility
    cat > lambda-src/handler.py <<'EOF'
    def main(event, context):
        return {"statusCode": 200, "body": "hello from terraformlab"}
    EOF
    ls lambda-src/

    Next, the Terraform part. In main.tf:

    hcl
    terraform {
      required_providers {
        archive = {
          source  = "hashicorp/archive"
          version = "~> 2.6"
        }
        random = {
          source  = "hashicorp/random"
          version = "~> 3.6"
        }
        time = {
          source  = "hashicorp/time"
          version = "~> 0.12"
        }
      }
    }
    data "archive_file" "lambda" {
      type        = "zip"
      source_file = "${path.module}/lambda-src/handler.py"
      output_path = "${path.module}/lambda.zip"
    }

    Run init plus a plan:

    bash
    terraform init
    terraform plan

    The plan shows that data.archive_file will be read. The provider itself creates the lambda.zip file during apply.

    ✓ archive_file is in place. Now we add the rest of the providers.

  2. 02

    random for a unique name, time for a timestamp

    Add this to main.tf:

    hcl
    resource "random_id" "fn_suffix" {
      byte_length = 4
    }
    resource "random_password" "api_key" {
      length  = 24
      special = false
    }
    resource "time_static" "created_at" {}
    locals {
      function_name = "linuxlab-utility-${random_id.fn_suffix.hex}"
      created_date  = formatdate("YYYY-MM-DD", time_static.created_at.rfc3339)
    }

    What happens here:

    • random_id.fn_suffix.hex, 8 hex characters, unique at creation, stable between applies (written into state).
    • random_password.api_key.result, a password of length 24, no special characters. Marked sensitive automatically.
    • time_static.created_at, pins a timestamp at the first apply. It does not change after that.

    See tf-utility-providers.

    ✓ Random plus time are in place. Now we create the Lambda and put it on S3.

  3. 03

    Create the Lambda with the packaged code

    Add this to main.tf:

    hcl
    resource "aws_iam_role" "lambda" {
      name = "${local.function_name}-role"
      assume_role_policy = jsonencode({
        Version = "2012-10-17"
        Statement = [{
          Action = "sts:AssumeRole"
          Effect = "Allow"
          Principal = { Service = "lambda.amazonaws.com" }
        }]
      })
    }
    resource "aws_lambda_function" "demo" {
      function_name    = local.function_name
      filename         = data.archive_file.lambda.output_path
      source_code_hash = data.archive_file.lambda.output_base64sha256
      handler          = "handler.main"
      runtime          = "python3.12"
      role             = aws_iam_role.lambda.arn
      environment {
        variables = {
          API_KEY      = random_password.api_key.result
          CREATED_DATE = local.created_date
        }
      }
      tags = {
        CreatedAt = local.created_date
      }
    }
    output "function_name" {
      value = aws_lambda_function.demo.function_name
    }

    The key points:

    • source_code_hash = data.archive_file.lambda.output_base64sha256 triggers a Lambda update when the code changes. Without it, Terraform does not notice edits in handler.py.
    • random_password.api_key.result goes into the Lambda env. It is in plain text in state.
    • local.created_date, a local computed from time_static.
    bash
    terraform apply -auto-approve

    ✓ The Lambda is created with a unique name and a timestamp.

  4. 04

    Change the Lambda code: autoupdate

    Let's show that source_code_hash works by changing the handler code.

    bash
    cat > lambda-src/handler.py <<'EOF'
    def main(event, context):
        return {"statusCode": 200, "body": "hello v2"}
    EOF
    terraform plan

    The plan shows:

    # aws_lambda_function.demo will be updated in-place
      ~ source_code_hash = "..." -> "..."

    Terraform saw that the hash changed (because the code changed) and wants to update the Lambda. Without source_code_hash it would not notice, since the filename is the same.

    bash
    terraform apply -auto-approve

    The Lambda is redeployed with the new code.

    ✓ The Lambda is updated. archive_file plus source_code_hash works.

    The same thing on OpenTofu

    OpenTofu keeps the CLI and state compatible with Terraform for the commands in this step: migration usually goes through mv .terraform .terraform.bak; tofu init -upgrade. On a first switch, though, back up the state and do a run on a feature branch, the differences cluster in the newer features (variables in backend, state encryption, OCI registry-backed modules). See tf-opentofu-parity for the full matrix.

    • → OpenTofu parity
  5. 05

    Forced regeneration of random_id

    Sometimes you want a new suffix, a new bucket, everything fresh. Without an HCL change, random_id stays in state and does not change. Force it:

    bash
    terraform apply -auto-approve -replace=random_id.fn_suffix

    The plan shows:

    # random_id.fn_suffix will be replaced (due to -replace)

    And in a cascade, every resource that depends on it (the Lambda, the IAM role, because the name contains random_id.fn_suffix.hex):

    # aws_iam_role.lambda will be destroyed
    # aws_iam_role.lambda will be created (new name)
    ...

    The apply goes through, and everything is recreated with the new suffix. This is the classic case of a stale pet name that you want fresh.

    ✓ Regenerating random worked in a cascade.

    external: when no provider fits

    Sometimes you need to read a value from a source that is not among the AWS data sources: a git commit hash, a secret from an internal store, the result of an HTTP request with auth.

    hcl
    terraform {
      required_providers {
        external = {
          source  = "hashicorp/external"
          version = "~> 2.3"
        }
      }
    }
    data "external" "build_info" {
      program = ["bash", "${path.module}/scripts/build-info.sh"]
    }
    resource "aws_lambda_function" "demo" {
      # ...
      tags = {
        GitCommit = data.external.build_info.result.commit
      }
    }

    The build-info.sh script:

    bash
    #!/usr/bin/env bash
    set -euo pipefail
    commit=$(git -C / rev-parse HEAD 2>/dev/null || echo "unknown")
    jq -n --arg c "$commit" '{commit: $c}'

    The important parts:

    • The script is called on every plan, so it must be idempotent.
    • All values in the result are strings. Convert numbers with tonumber(data.external.x.result.count).
    • The script runs with the permissions of the terraform user. This is a security risk, so do not use it with untrusted HCL.

    See tf-archive-external-http for all three data providers.

    • → Utility providers in full
    • → archive, external, http

Что ты узнал

random, unique IDs and passwords (sensitive in state). time, timestamps and delays. archive_file, packaging files into a zip for Lambda and Layers. external, run an arbitrary script and exchange JSON. All four are HashiCorp-official, stable, with no dependency on the AWS API.

команды

  • terraform apply -replace=random_id.suffixforced regeneration: the cascade recreates everything that depends on it
  • terraform state show random_password.userthe password is visible in state: sensitive only in the output
  • terraform planexternal/archive are computed on every plan: keep that in mind

концепции

  • · random values are pinned in state: stable between applies
  • · archive_file: source_code_hash triggers a Lambda update when the code changes
  • · external: a last resort: the script must be idempotent and return only string values

← предыдущий

HCL hygiene: fmt, validate, console

следующий →

Debugging. TF_LOG, the graph, reading someone else's error

Footer
linuxlab-
Copyright © 2026 LinuxLab. All rights reserved.
Tutorials
Pricing
About
Privacy & cookies