Terraform actions — это новая концепция языка в Terraform 1.14, официально анонсированная на HashiConf 2025 в Сан-Франциско. Terraform actions разрабатывались открыто, так что вы, возможно, уже замечали их в примечаниях к выпускам (как для самого Terraform, так и для провайдеров, языковых серверов, плагинов VS Code и т.д.).

В этой статье я расскажу, что такое Terraform actions и как они работают. Попутно мы рассмотрим несколько доступных действий из провайдера AWS, которые вы можете начать использовать уже сегодня.

Примечание

В этой статье я рассматриваю действия только в контексте конфигурации Terraform и Terraform CLI. Действия также полностью поддерживаются в HCP Terraform (для режимов VCS, CLI и API).

Что такое Terraform actions?

Terraform — это, по своей сути, инструмент для декларативного подхода «инфраструктура как код». Вы пишете конфигурацию, представляющую желаемое состояние вашей инфраструктуры, а Terraform следит за тем, чтобы реальное состояние соответствовало желаемому. Terraform взаимодействует с управляющими API облака, подготавливая ресурсы, с которыми вы затем можете работать другими способами.

С помощью Terraform actions теперь можно выполнять операции вне обычного CRUD (Create-Read-Update-Delete) рабочего процесса Terraform. Это позволяет выполнять операции, взаимодействующие с вашими ресурсами способами, для которых вы обычно используете другие инструменты, например, Ansible. Обратите внимание, что Terraform actions не заменяют Ansible; скорее, вы будете запускать действия Ansible (например, выполнение playbook) из Terraform.

Вы можете выполнять автономные действия, запускаемые через Terraform CLI, или привязывать действия к жизненному циклу ресурса.

Примеры действий которые уже доступны сегодня это вызов функции AWS Lambda, остановка инстанса AWS EC2 и сброс кэша AWS CloudFront. Примеры каждого из них мы рассмотрим далее в этой статье.

Другой способ взглянуть на Terraform actions — это новый декларативный способ выполнения операций, для которых раньше приходилось использовать ресурс terraform_data вместе с блоком provisioner, запускающим локальный скрипт:

resource "terraform_data" "example" {
  triggers_replace = [
    timestamp(),
  ]

  provisioner "local-exec" {
    command = "scripts/my-script.sh"
  }
}

Действия реализуются провайдерами, и, вероятно, на начальном этапе количество доступных действий будет невелико.

Как работают Terraform actions?

Terraform actions вводят в язык Terraform новый блок action, который вы можете использовать в своих конфигурациях. Блоки action добавляются в те же .tf файлы, что и остальная часть вашей конфигурации Terraform[*].

Базовый синтаксис блока action выглядит следующим образом:

action "<тип действия>" "<символическое имя>" {
  # мета-аргументы: provider, count, for_each
  config {
    # конфигурация действия, зависящая от его типа
  }
}

Разберем блок action:

  • <тип действия> представляет тип действия, определенный в провайдере Terraform. Имя каждого типа действия имеет префикс с названием провайдера, например, действие aws_lambda_invoke из провайдера AWS.

  • <символическое имя> похоже на то, как определяются ресурсы и источники данных; оно позволяет настраивать несколько действий одного типа, если их символические имена различаются. Хорошей практикой является использование имени, состоящего из строчных букв, цифр и знаков подчеркивания.

  • Блок config содержит конфигурацию действия и может состоять из аргументов и вложенных блоков. Конфигурация будет отличаться для каждого типа действия.

Вы добавляете блоки action в свою конфигурацию Terraform и, при необходимости, привязываете их к жизненному циклу различных ресурсов. В следующих разделах мы увидим, как это делается.

При выполнении операций terraform plan и terraform apply Terraform сообщит вам, какие действия будут запущены (если таковые имеются) и в какой момент рабочего процесса они будут выполнены.

Некоторым действиям требуется время для завершения. Terraform будет ожидать, пока действие не сообщит о своем завершении.

Вызов действия с помощью Terraform CLI

Вы можете настроить блоки action в своей конфигурации Terraform, на которые нет ссылок в остальном коде. Я буду называть такие действия автономными действиями.

Автономные действия можно запустить только с помощью Terraform CLI. Это делается путем добавления флага -invoke=<адрес действия> к командам terraform plan и terraform apply. <адрес действия> имеет формат action.<тип действия>.<символическое имя>.

Адрес следующего действия — action.aws_lambda_invoke.message:

action "aws_lambda_invoke" "message" {
  config {
    function_name = "send-a-message"
    payload       = jsonencode({
      message = "This is the action payload!"
    })
  }
}

Это первый конкретный пример действия, который мы видим. Действие aws_lambda_invoke вызывает функцию Lambda (что неудивительно).

У действия aws_lambda_invoke много опций конфигурации, но в простейшем случае мы указываем, какую функцию вызывать (в аргументе function_name) и какие данные передавать в функцию (в аргументе payload). Полезная нагрузка не обязательно должна быть статической, как в этом примере; она может ссылаться на переменные, локальные значения, атрибуты ресурсов, источники данных и так далее.

Функция, на которую ссылается предыдущее действие (т.е. функция с именем send-a-message), должна уже существовать.

Вы можете создать ресурс Lambda в той же конфигурации Terraform, где настроено действие, и сослаться на aws_lambda_function.<символическое имя>.function_name в аргументе function_name действия.

Чтобы запустить действие из предыдущего блока кода с помощью Terraform CLI, выполните эту команду:

$ terraform apply \
    -auto-approve \
    -invoke=action.aws_lambda_invoke.message

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.message will be invoked
    action "aws_lambda_invoke" "message" {
        config {
          # сокращено ...
        }
    }

Action started: action.aws_lambda_invoke.message (triggered by CLI)
Action action.aws_lambda_invoke.message (triggered by CLI): Invoking Lambda function slack...
Action action.aws_lambda_invoke.message (triggered by CLI): Lambda function slack invoked successfully (status: 200, payload: 453 bytes)
Action complete: action.aws_lambda_invoke.message (triggered by CLI)

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Пользователь или сущность, выполняющая это действие, должна иметь политику AWS IAM, разрешающую вызов этой функции Lambda.

Обратите внимание, что эта команда запускает только действие, указанное во флаге -invoke, и ничего другого в конфигурации не применяется и не обновляется (включая ресурсы и другие действия).

Вы можете вызывать только одно действие за раз. Нельзя добавлять дополнительные флаги -invoke для запуска нескольких действий.

Хорошей практикой будет сначала выполнить операцию terraform plan, чтобы убедиться, что все в порядке (как и для всех рабочих процессов Terraform):

$ terraform plan -invoke=action.aws_lambda_invoke.message

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.message will be invoked
    action "aws_lambda_invoke" "message" {
        config {
          # сокращено для краткости ...
        }
    }

Любое действие можно запустить с помощью Terraform CLI, даже если оно привязано к жизненному циклу ресурса.

Если ваше действие ссылается на недоступные данные (например, на атрибут еще не созданного ресурса), его нельзя будет запустить как автономное действие.

Привязка действия к жизненному циклу ресурса

Вы можете привязать одно или несколько действий к событиям в жизненном цикле ресурса. Это достигается путем ссылки на действие из блока lifecycle ресурса.

Псевдо-пример того, как это настраивается, показан в следующем блоке кода:

resource "<тип ресурса>" "<символическое имя>" {
  # аргументы опущены ...

  lifecycle {
    action_trigger {
      events  = [<тип(ы) события>]
      actions = [<действие(я)>]
    }
  }
}

Разберем этот пример кода:

  • Внутри блока lifecycle ресурса появился новый поддерживаемый блок: action_trigger.

  • В аргументе events вы можете указать одно или несколько событий, которые должны запускать действие(я). Доступные события: before_create, after_create, before_update и after_update.

  • В аргументе actions вы можете указать одно или несколько действий, которые должны запускаться на основе событий. Каждое действие настраивается с помощью его адреса, т.е. action.<тип действия>.<символическое имя>.

Вы можете указать несколько событий и несколько действий в одном блоке action_trigger. Вы также можете настроить несколько блоков action_trigger в одном блоке lifecycle ресурса. Примеры того, как это работает, см. в разделе Объединение нескольких действий в цепочку далее в этой статье.

Полный пример действия, привязанного к жизненному циклу инстанса AWS EC2, показан ниже:

action "aws_lambda_invoke" "notifier" {
  config {
    function_name = "notifier"
    payload = jsonencode({
      message = "An EC2 with ID ${aws_instance.web.id} has been created."
    })
  }
}

resource "aws_instance" "web" {
  ami           = "ami-091a906f2e1e40076"
  instance_type = "t3.small"

  tags = {
    Name = "web"
  }

  lifecycle {
    action_trigger {
      events    = [after_create]
      actions   = [action.aws_lambda_invoke.notifier]
    }
  }
}

Как мы видим, блок lifecycle ресурса aws_instance ссылается на action.aws_lambda_invoke.notifier. Действие, в свою очередь, также ссылается на ресурс aws_instance — не создаст ли это цикл зависимостей? Нет, потому что действие запускается после создания ресурса.

Однако это означает, что данное конкретное действие не может быть запущено до того, как ресурс будет создан. То есть, если мы попытаемся запустить его через CLI как автономное действие, мы столкнемся с такой ошибкой:

$ terraform apply -auto-approve -invoke=action.aws_lambda_invoke.notifier

Error: Action configuration unknown during apply
│
│ The action action.aws_lambda_invoke.notifier was not fully known during apply.

Условный запуск действия

Вы можете дополнительно контролировать, будет ли действие вызвано, используя выражение condition внутри блока action_trigger. Если выражение вычисляется как true, действие будет запущено.

Мы можем расширить предыдущий пример, чтобы вызывать функцию Lambda только в том случае, если переменная с именем send_notifications установлена в true:

variable "send_notifications" {
  type    = bool
  default = false
}

action "aws_lambda_invoke" "notifier" {
  config {
    function_name = "notifier"
    payload = jsonencode({
      message = "An EC2 with ID ${aws_instance.web.id} has been created."
    })
  }
}

resource "aws_instance" "web" {
  ami           = "ami-091a906f2e1e40076"
  instance_type = "t3.small"

  tags = {
    Name = "web"
  }

  lifecycle {
    action_trigger {
      condition = var.send_notifications
      events    = [after_create]
      actions   = [action.aws_lambda_invoke.notifier]
    }
  }
}

Значение выражения condition должно быть известно на этапе планирования (plan). Это означает, что вы не можете использовать значения функций, которые могут измениться между операциями plan и apply (например, timestamp()).

Использование мета-аргументов с действиями

Блок action поддерживает мета-аргументы provider, count и for_each.

Аргумент provider позволяет выполнять действия в контексте определенной конфигурации провайдера. Как и в обычных конфигурациях Terraform, аргумент provider не требуется, если у вас есть только один провайдер (провайдер по умолчанию) с данным именем. Если у вас несколько провайдеров с одинаковым именем, вы можете использовать псевдонимы чтобы их различать и для запуска действий через каждого провайдера:

provider "aws" {
  alias = "dev"
  # ...
}

provider "aws" {
  alias = "prod"
  # ...
}

action "aws_lambda_invoke" "dev" {
  provider = aws.dev
  
  config {
    function_name = "dev-function"
    payload = {
      # ...
    }
  }
}

action "aws_lambda_invoke" "prod" {
  provider = aws.prod
  
  config {
    function_name = "prod-function"
    payload = {
      # ...
    }
  }
}

Мета-аргументы count и for_each более интересны. Вы можете использовать мета-аргумент count для автономных действий, как в следующем примере:

action "aws_lambda_invoke" "counter" {
  count = 10

  config {
    function_name = "counter"
    payload       = count.index
  }
}

Это действие вызывает функцию Lambda с именем counter, которая принимает целое число в качестве полезной нагрузки. Предполагается, что эта функция уже существует.

Если мы вызовем это действие с помощью Terraform CLI, мы получим следующий результат (обратите внимание, что вывод сокращен для краткости):

$ terraform apply -auto-approve -invoke=action.aws_lambda_invoke.counter
Terraform will perform the following actions:

Plan: 0 to add, 0 to change, 0 to destroy. Actions: 10 to invoke.

Terraform will invoke the following action(s):

  # action.aws_lambda_invoke.counter[7] will be invoked
    action "aws_lambda_invoke" "counter" {
        config {
            function_name = "counter"
            payload       = "7"
        }
    }

    ...

  # action.aws_lambda_invoke.counter[6] will be invoked
    action "aws_lambda_invoke" "counter" {
        config {
            function_name = "counter"
            payload       = "6"
        }
    }

...
Action started: action.aws_lambda_invoke.counter[4] (triggered by CLI)
... (сокращено) ...
Action started: action.aws_lambda_invoke.counter[3] (triggered by CLI)
Action action.aws_lambda_invoke.counter[5] (triggered by CLI): Invoking Lambda function counter...
... (сокращено) ...
Action action.aws_lambda_invoke.counter[8] (triggered by CLI): Invoking Lambda function counter...
Action action.aws_lambda_invoke.counter[0] (triggered by CLI): Lambda function counter invoked successfully (status: 200, payload: 4 bytes)
Action complete: action.aws_lambda_invoke.counter[0] (triggered by CLI)
... (сокращено) ...
Action action.aws_lambda_invoke.counter[2] (triggered by CLI): Lambda function counter invoked successfully (status: 200, payload: 4 bytes)
Action complete: action.aws_lambda_invoke.counter[2] (triggered by CLI)

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

Предыдущая команда запустила все экземпляры действия. Вы можете запустить конкретный экземпляр, используя селектор индекса:

$ terraform apply -auto-approve -invoke='action.aws_lambda_invoke.counter[0]'
...

Мета-аргумент for_each позволяет запускать действия для каждого ресурса, который также создается с помощью мета-аргумента for_each. Это означает, что вы можете сопоставить ресурс с соответствующим действием.

Пример создания нескольких инстансов EC2 с помощью for_each и вызова соответствующего действия для каждого ресурса EC2:

# определяем локальное значение для инстансов EC2, которые будут созданы
locals {
  instances = {
    web = {
      ami  = "ami-091a906f2e1e40076"
      size = "t3.small"
    }
    db = {
      ami  = "ami-0bc691261a82b32bc"
      size = "t3.medium"
    }
  }
}

# настраиваем действия для каждого ключа из локального значения
# т.е. action.aws_lambda_invoke.slack["web"] и action.aws_lambda_invoke.slack["db"]
action "aws_lambda_invoke" "slack" {
  for_each = local.instances

  config {
    function_name = "slack"

    payload = jsonencode({
      title = "EC2 Instance ${each.key}"
      body  = "Instance ${each.key} has been created/updated with ID: ${aws_instance.servers[each.key].id}"
    })
  }
}

# настраиваем инстансы EC2, используя for_each с локальным значением
# т.е. aws_instance.servers["web"] и aws_instance.servers["db"]
resource "aws_instance" "servers" {
  for_each = local.instances

  ami           = each.value.ami
  instance_type = each.value.size

  tags = {
    Name = each.key
  }

  lifecycle {
    action_trigger {
      # запускаем действие после первоначального создания
      events  = [after_create]

      # запускаем действие, используя ключ из локального значения
      # например, aws_instance.servers["web"] запускает action.aws_lambda_invoke.slack["web"]
      actions = [action.aws_lambda_invoke.slack[each.key]]
    }
  }
}

Вы могли бы сделать то же самое, используя count, но с числовыми индексами вместо строк.

Объединение нескольких действий в цепочку

Блок lifecycle ресурса не ограничен вызовом одного действия. Вы можете добавлять несколько действий для каждого блока action_trigger и даже добавлять дополнительные блоки action_trigger.

Действия, которые должны выполняться после одного и того же события (например, after_create), можно сгруппировать в одном блоке action_trigger. Пример запуска трех разных действий (вызова трех разных функций Lambda) после создания инстанса EC2 можно настроить так:

action "aws_lambda_invoke" "first" {
  config {
    function_name = "first"
    payload       = jsonencode({
      # ...
    })
  }
}

action "aws_lambda_invoke" "second" {
  config {
    function_name = "second"
    payload       = jsonencode({
      # ...
    })
  }
}

action "aws_lambda_invoke" "third" {
  config {
    function_name = "third"
    payload       = jsonencode({
      # ...
    })
  }
}

resource "aws_instance" "web" {
  ami           = "ami-091a906f2e1e40076"
  instance_type = "t3.small"

  lifecycle {
    action_trigger {
      events = [after_create]
      actions = [
        action.aws_lambda_invoke.first,
        action.aws_lambda_invoke.second,
        action.aws_lambda_invoke.third,
      ]
    }
  }
}

Того же можно достичь, используя три отдельных блока action_trigger:

# предыдущий код опущен ...
resource "aws_instance" "web" {
  ami           = "ami-091a906f2e1e40076"
  instance_type = "t3.small"

  tags = {
    Name = "web"
  }

  lifecycle {
    action_trigger {
      events = [after_create]
      actions = [
        action.aws_lambda_invoke.first,
      ]
    }

    action_trigger {
      events = [after_create]
      actions = [
        action.aws_lambda_invoke.second,
      ]
    }

    action_trigger {
      events = [after_create]
      actions = [
        action.aws_lambda_invoke.third,
      ]
    }
  }
}

Для краткости следует предпочесть первый пример (объединение действий, запускаемых одним и тем же типом события, в одном блоке action_trigger).

Если у вас разные наборы действий для разных типов событий, вы должны использовать по одному блоку action_trigger для каждого типа события:

# предыдущий код опущен ...

resource "aws_instance" "web" {
  ami           = "ami-091a906f2e1e40076"
  instance_type = "t3.small"

  lifecycle {
    action_trigger {
      events = [before_create]
      actions = [
        action.aws_lambda_invoke.first,
      ]
    }

    action_trigger {
      events = [after_create]
      actions = [
        action.aws_lambda_invoke.first,
        action.aws_lambda_invoke.second,
      ]
    }

    action_trigger {
      events = [before_update]
      actions = [
        action.aws_lambda_invoke.second,
      ]
    }

    action_trigger {
      events = [after_update]
      actions = [
        action.aws_lambda_invoke.second,
        action.aws_lambda_invoke.third,
      ]
    }
  }
}

Длительные действия

Действие aws_lambda_invoke, которое мы уже несколько раз видели в этой статье, обычно выполняется быстро. Это предполагает, что функция Lambda довольно проста. Как выглядит работа с длительными действиями?

В провайдере AWS есть действие для корректной остановки инстансов EC2. Это может быть полезно, если вы хотите создать инстансы EC2 и настроить их (например, с помощью скриптов userdata), но планируете оставить их остановленными до более позднего времени.

В следующем примере мы используем локальное значение (с именем instances) для настройки параметров двух разных инстансов EC2. Затем мы используем это локальное значение в for_each вместе с типом ресурса aws_instance. В блоке lifecycle инстансов EC2 мы указываем, что действие aws_ec2_stop_instance.all[each.key] должно быть вызвано после создания инстанса (events = [after_create]):

locals {
  instances = {
    web = {
      ami  = "ami-091a906f2e1e40076"
      size = "t3.small"
    }
    db = {
      ami  = "ami-0bc691261a82b32bc"
      size = "t3.medium"
    }
  }
}

action "aws_ec2_stop_instance" "all" {
  for_each = local.instances

  config {
    instance_id = aws_instance.servers[each.key].id
  }
}

resource "aws_instance" "servers" {
  for_each = local.instances

  ami           = each.value.ami
  instance_type = each.value.size

  tags = {
    Name = each.key
  }

  lifecycle {
    action_trigger {
      events  = [after_create]
      actions = [action.aws_ec2_stop_instance.all[each.key]]
    }
  }
}

Если вы выполните terraform apply для этой конфигурации Terraform, вы увидите, как действие запускается для каждого инстанса EC2, а затем Terraform опрашивает состояние инстанса EC2, пока тот не сообщит, что он stopped (для краткости в выводе показан только инстанс db):

$ terraform apply
...
Action action.aws_ec2_stop_instance.all["db"] (triggered by aws_instance.servers["db"]): EC2 instance i-006c09cd899162829 is currently in state 'stopping', continuing to wait for 'stopped'...
Action action.aws_ec2_stop_instance.all["db"] (triggered by aws_instance.servers["db"]): EC2 instance i-006c09cd899162829 is currently in state 'stopping', continuing to wait for 'stopped'...
Action action.aws_ec2_stop_instance.all["db"] (triggered by aws_instance.servers["db"]): EC2 instance i-006c09cd899162829 is currently in state 'stopping', continuing to wait for 'stopped'...
Action action.aws_ec2_stop_instance.all["db"] (triggered by aws_instance.servers["db"]): EC2 instance i-006c09cd899162829 is currently in state 'stopping', continuing to wait for 'stopped'...
Action action.aws_ec2_stop_instance.all["db"] (triggered by aws_instance.servers["db"]): EC2 instance i-006c09cd899162829 is currently in state 'stopping', continuing to wait for 'stopped'...
Action action.aws_ec2_stop_instance.all["db"] (triggered by aws_instance.servers["db"]): EC2 instance i-006c09cd899162829 is currently in state 'stopping', continuing to wait for 'stopped'...
Action action.aws_ec2_stop_instance.all["db"] (triggered by aws_instance.servers["db"]): EC2 instance i-006c09cd899162829 is currently in state 'stopping', continuing to wait for 'stopped'...
Action action.aws_ec2_stop_instance.all["db"] (triggered by aws_instance.servers["db"]): EC2 instance i-006c09cd899162829 has been successfully stopped
Action complete: action.aws_ec2_stop_instance.all["db"] (triggered by aws_instance.servers["db"])

Следует упомянуть, что в провайдере AWS есть ресурс, который позволяет устанавливать состояние инстанса EC2:

resource "aws_ec2_instance_state" "test" {
  instance_id = aws_instance.test.id
  state       = "stopped"
}

Преимущество действия aws_ec2_stop_instance в том, что оно корректно останавливает инстанс. Для ресурса aws_ec2_instance_state это не так.


Еще одно действие, которое потенциально может занять некоторое время, — это сброс кэша CloudFront. Для выполнения этой операции доступно действие aws_cloudfront_create_invalidation.

В следующей конфигурации Terraform мы создаем дистрибуцию AWS CloudFront с источником в S3. Мы добавляем страницу index.html в бакет S3. Если страница index.html обновляется, мы хотим сбросить кэш CloudFront, чтобы новое HTML-содержимое сразу отображалось пользователям. Полный пример выходит за рамки этой статьи, но соответствующая конфигурация показана ниже:

# создаем объект index.html в бакете S3
resource "aws_s3_object" "index_html" {
  bucket         = aws_s3_bucket.default.bucket
  key            = "index.html"
  content_type   = "text/html"
  content_base64 = filebase64("${path.module}/html/index.html")

  lifecycle {
    action_trigger {
      # запускаем действие по сбросу кэша после обновления этого объекта
      events  = [after_update]

      # вызываем действие cloudfront
      actions = [action.aws_cloudfront_create_invalidation.update]
    }
  }
}

action "aws_cloudfront_create_invalidation" "update" {
  config {
    distribution_id = aws_cloudfront_distribution.default.id

    # указываем, какие пути сбрасывать, или используем "/*" для всех путей
    paths           = ["/*"]
  }
}

При первоначальном terraform apply никакое действие не будет вызвано. Обновление содержимого index.html и запуск нового terraform plan дает следующие результаты:

$ terraform plan
# ...
Terraform will perform the following actions:

  # aws_s3_object.index_html will be updated in-place
  ~ resource "aws_s3_object" "index_html" {
      ~ content_base64                = "..." # сокращено
        id                            = "actions20250921153319508800000001/index.html"
        tags                          = {}
      + version_id                    = (known after apply)
        # (25 неизмененных атрибутов скрыто)
    }

    # Действия, которые будут вызваны после этого изменения по порядку:
    action "aws_cloudfront_create_invalidation" "update" {
        config {
            distribution_id = "E1ATESYZBVWG3G"
            paths           = [
                "/*",
            ]
        }
    }


Plan: 0 to add, 1 to change, 0 to destroy. Actions: 1 to invoke.

Будет вызвано одно действие. Применяем этот план:

$ terraform apply
# ...
aws_s3_object.index_html: Modifying... [id=actions20250921153319508800000001/index.html]
aws_s3_object.index_html: Modifications complete after 1s [id=actions20250921153319508800000001/index.html]
Action started: action.aws_cloudfront_create_invalidation.update (triggered by aws_s3_object.index_html)
Action action.aws_cloudfront_create_invalidation.update (triggered by aws_s3_object.index_html): Starting cache invalidation for CloudFront distribution E1ATESYZBVWG3G...
Action action.aws_cloudfront_create_invalidation.update (triggered by aws_s3_object.index_html): Creating invalidation request for 1 path(s)...
Action action.aws_cloudfront_create_invalidation.update (triggered by aws_s3_object.index_html): Invalidation IDFZHCUYBIIM0TTA0LX0ICX0M5 created, waiting for completion...
Action action.aws_cloudfront_create_invalidation.update (triggered by aws_s3_object.index_html): CloudFront cache invalidation IDFZHCUYBIIM0TTA0LX0ICX0M5 completed successfully for distribution E1ATESYZBVWG3G
Action complete: action.aws_cloudfront_create_invalidation.update (triggered by aws_s3_object.index_html)

Apply complete! Resources: 0 added, 1 changed, 0 destroyed.

Поскольку бакет S3 содержит только один HTML-файл, это действие выполняется довольно быстро.

Зависимость от результата действия

На момент написания статьи, похоже, нет поддержки зависимости от результата действия. Это означает, что вы не можете сделать что-то вроде этого:

action "aws_lambda_invoke" "dummy" {
  config {
    # ...
  }
}

resource "..." "..." {
  # аргументы опущены ...

  depends_on = [
    action.aws_lambda_invoke.dummy,
  ]
}

Причина этого, скорее всего, в том, что результат вызова действия не может быть известен Terraform заранее на этапе планирования.

Другая вещь которую вы можете попробовать, — это зависимость от ресурса, к которому привязано данное действие, что-то вроде этого:

action "aws_lambda_invoke" "dummy" {
  config {
    # ...
  }
}

resource "aws_instance" "dummy" {
  # аргументы опущены ...

  lifecycle {
    action_trigger {
      events  = [after_create]
      actions = [action.aws_lambda_invoke.dummy]
    }
  }
}

resource "aws_s3_bucket" "dummy" {
  # аргументы опущены ...

  depends_on = [
    aws_instance.dummy,
  ]
}

Однако, как только инстанс EC2 (aws_instance.dummy) будет создан, он запустит действие (aws_lambda_invoke.dummy) и начнет создание бакета S3 (aws_s3_bucket.dummy) параллельно.

Итак, в итоге: вы не можете зависеть от результата действия в последующем коде Terraform.

Ключевые выводы

Terraform actions позволяют расширять ваши конфигурации Terraform действиями, выходящими за рамки обычных CRUD-операций с вашими ресурсами. Terraform actions представляют собой декларативные операции, которые вы можете выполнять как автономные действия или привязывать их к жизненному циклу ресурса.

В этой статье мы узнали, что такое действия, как они работают, и увидели несколько примеров действий, доступных в провайдере AWS для Terraform.

Действия настраиваются с помощью нового блока action. Вы можете вызывать автономные действия с помощью terraform apply -invoke=<адрес действия>. Блок lifecycle ресурса теперь поддерживает новый блок action_trigger, где вы можете привязывать действия к жизненному циклу ресурса.

Я считаю, что в будущем мы увидим большой action (каламбур) по продвижению пространства Terraform actions.

Узнать больше