Schedule a CloudFlow workflow by using Terraform

Updated at:

CloudFlow scheduling starts a workflow on a time-based trigger and is implemented based on EventBridge. CloudFlow provides no API for scheduling, so Terraform cannot create a CloudFlow schedule directly. Use Terraform to create the EventBridge resources that CloudFlow scheduling is built on.

Terraform is an open source tool that you can use to preview, configure, and manage cloud infrastructure and resources. Because CloudFlow scheduling is currently available only in the console, you have two paths to a scheduled workflow:

  • CloudFlow console — Configure scheduling for the workflow directly in the CloudFlow console.

  • Terraform — Create the EventBridge resources that CloudFlow scheduling is implemented on, and manage them as code together with the rest of your infrastructure. This topic describes this path.

    The sample code in this topic supports one-click deployment. Run in one click.

Prerequisites

  • Activate CloudFlow.

  • Activate EventBridge.

  • Use a Resource Access Management (RAM) user and create an AccessKey pair for the RAM user. An Alibaba Cloud account has all permissions on its resources, and leaked account credentials expose you to significant risks. For more information, see Create a RAM user and Create an AccessKey.

  • Grant the AliyunFnFFullAccess permission to the RAM user to manage CloudFlow (FNF) resources. For more information, see Manage RAM user permissions.

  • Prepare a Terraform runtime environment. Use one of the following options:

    • (Recommended) Use Terraform in Terraform Explorer: Alibaba Cloud provides an online runtime environment for Terraform. You do not need to install Terraform. After you log on, you can use and try out Terraform online. This method is suitable for scenarios in which you want to try out and debug Terraform quickly and conveniently at no cost.

    • Create resources with Terraform: Terraform components are preinstalled in Alibaba Cloud Cloud Shell and the identity credentials are configured. You can run Terraform commands directly in Cloud Shell. This method is suitable for scenarios in which you want to access and use Terraform quickly and conveniently at a low cost.

    • Install and configure Terraform on your on-premises machine: This method is suitable for scenarios in which the network connectivity is poor or you need a custom development environment.

Important

Use Terraform v0.12.28 or later. To check the version that is installed, run the terraform --version command.

Note

The sample code creates CloudFlow, RAM, Simple Message Queue (formerly MNS), and EventBridge resources. Make sure that the identity you run Terraform with can create every resource type that the Resources used section lists.

Resources used

Create the scheduling resources with Terraform

  1. Create a working directory and create a configuration file named main.tf in it. main.tf is the main Terraform file. It defines the resources to be deployed.

    The region variable sets the region for all resources in the configuration. The sample uses cn-hangzhou as the default value, so change the default value to the region that you use.

    variable "region" {
      default = "cn-hangzhou"
    }
    provider "alicloud" {
      region = var.region
    }
    # Define the name variable.
    variable "name" {
      default = "test-mns"
    }
    # Define the policy name variable.
    variable "policy_name" {
      type        = string
      description = "The name of the policy."
      default     = "test-policy"
    }
    # Define the role name variable.
    variable "role_name" {
      type        = string
      description = "The role for eb to start execution of flow."
      default     = "eb-to-fnf-role"
    }
    # Define the CloudFlow name variable.
    variable "flow_name" {
      type        = string
      description = "The name of the flow."
      default     = "test-flow"
    }
    # Define the description of the flow.
    variable "flow_description" {
      default = "For flow_description"
    }
    # Define the event bus name variable.
    variable "event_bus_name" {
      type        = string
      description = "The name of the event bus."
      default     = "test-eventbus1"
    }
    # Define the event bus description variable.
    variable "event_bus_description" {
      default = "For event_bus_description"
    }
    # Define the code name variable of the event source.
    variable "event_source_name" {
      type        = string
      description = "The name of the event source."
      default     = "test-eventsource1"
    }
    # Define the name of the event rule.
    variable "event_rule_name" {
      type        = string
      description = "The name of the event rule."
      default     = "test-eventrule1"
    }
    # The custom ID of the event target.
    variable "target_id" {
      type        = string
      description = "The ID of the target."
      default     = "test-target1"
    }
    # Obtain the UID of the current Alibaba Cloud account.
    data "alicloud_account" "current" {
    }
    # Create a random number.
    resource "random_integer" "default" {
      min = 10000
      max = 99999
    }
    # Create a RAM policy to define permissions.
    resource "alicloud_ram_policy" "policy_example" {
      # The name of the RAM policy.
      policy_name     = "${var.policy_name}-${random_integer.default.result}"
      # (Optional) This parameter is used for resource destruction. Default value: false.
      force           = true
      # The document of the RAM policy.
      policy_document = <<EOF
      {
        "Statement": [
          {
            "Action": [
              "fnf:*",
              "mns:*",
              "eventbridge:*",
              "ram:*"
            ],
            "Effect": "Allow",
            "Resource": [
              "*"
            ]
          }
        ],
        "Version": "1"
      }
      EOF
    }
    # Create a RAM role.
    resource "alicloud_ram_role" "role_example" {
      # The name of the role.
      name     = var.role_name
      # (Optional) This parameter is used for resource destruction. Default value: false.
      force    = true
      # The policy document of the role.
      document = <<EOF
      {
        "Statement": [
          {
            "Action": "sts:AssumeRole",
            "Effect": "Allow",
            "Principal": {
              "Service": [
                "fnf.aliyuncs.com"
              ]
            }
          }
        ],
        "Version": "1"
      }
      EOF
    }
    # Attach the created RAM policy to the RAM role to grant the corresponding permissions to the role.
    resource "alicloud_ram_role_policy_attachment" "attach_example" {
      # The name of the RAM policy.
      policy_name = alicloud_ram_policy.policy_example.policy_name
      # The type of the RAM policy.
      policy_type = alicloud_ram_policy.policy_example.type
      # The name of the RAM role.
      role_name   = alicloud_ram_role.role_example.name
    }
    # Create a CloudFlow resource.
    resource "alicloud_fnf_flow" "flow_example" {
      depends_on  = [alicloud_ram_role_policy_attachment.attach_example]
      # (Required) The definition of the flow. The definition must conform to the Flow Definition Language (FDL) syntax.
      definition  = <<EOF
      Type: StateMachine
      Name: ${var.flow_name}
      SpecVersion: v1
      StartAt: Hello World
      States:
      - Type: Pass
        Name: Hello World
        End: true
      EOF
      # The ARN of the specified RAM role that CloudFlow uses to execute the flow.
      role_arn    = alicloud_ram_role.role_example.arn
      # The description of the flow.
      description = var.flow_description
      # The name of the flow.
      name        = var.flow_name
      # The type of the flow. Valid values: FDL and DEFAULT.
      type        = "FDL"
    }
    # Create an event bus to receive and route events.
    resource "alicloud_event_bridge_event_bus" "eventbus_example" {
      # The name of the event bus.
      event_bus_name = var.event_bus_name
      # (Optional) The description of the event bus.
      description    = var.event_bus_description
    }
    # Create a Simple Message Queue (formerly MNS) queue.
    resource "alicloud_mns_queue" "example" {
      # The name.
      name                     = "${var.name}-${random_integer.default.result}"
      # This property defines the delay, in seconds, before a message sent to the queue can be dequeued.
      delay_seconds            = 0
      # This property specifies the maximum size, in bytes, of a message body sent to the queue.
      maximum_message_size     = 65536
      # This property defines the retention period, in seconds, of each message in the queue.
      message_retention_period = 345600
      # The visibility timeout property of the queue.
      visibility_timeout       = 30
      # The long polling time, in seconds.
      polling_wait_seconds     = 0
    }
    # Create an event source and link it to the Simple Message Queue (formerly MNS) queue.
    resource "alicloud_event_bridge_event_source" "eventsource_example" {
      # The name of the event bus.
      event_bus_name         = alicloud_event_bridge_event_bus.eventbus_example.event_bus_name
      # The code name of the event source.
      event_source_name      = var.event_source_name
      # (Optional, Computed) Specifies whether to connect to an external data source. Default value: false.
      linked_external_source = true
      # (Optional) The type of the external data source. Valid values: RabbitMQ, RocketMQ, and MNS. Note: This field is valid only when linked_external_source is set to true.
      external_source_type   = "MNS"
      # (Optional, Map) The configuration of the external source.
      external_source_config = {
        QueueName = alicloud_mns_queue.example.name
      }
    }
    # Define a local variable to store the ARN of the CloudFlow workflow.
    locals {
      flow_arn = format("acs:fnf:::flow/%s", var.flow_name)
    }
    # Create an event rule to match the events generated by the event source and route the events to the specified CloudFlow workflow.
    resource "alicloud_event_bridge_rule" "eventrule_example" {
      # The name of the event bus.
      event_bus_name = alicloud_event_bridge_event_bus.eventbus_example.event_bus_name
      # The name of the event rule.
      rule_name      = var.event_rule_name
      # The pattern that matches the events of interest. The event pattern is in JSON format. The values are described as follows: stringEqual pattern and stringExpression pattern.
      filter_pattern = format("{\"source\":[\"%s\"]}", var.event_source_name)
      # The targets of the rule.
      targets {
        # The custom ID of the event target.
        target_id = var.target_id
        # The endpoint of the event target.
        endpoint  = local.flow_arn
        # The type of the event target.
        type      = "acs.fnf"
        param_list {
          resource_key = "Input"
          form         = "ORIGINAL"
        }
        param_list {
          form         = "CONSTANT"
          resource_key = "FlowName"
          value        = var.flow_name
        }
        param_list {
          form         = "CONSTANT"
          resource_key = "RoleName"
          value        = var.role_name
        }
      }
    }
  2. Run the following command to initialize the Terraform runtime environment:

    terraform init
    Initializing the backend...
    Initializing provider plugins...
    - Finding latest version of hashicorp/alicloud...
    - Installing hashicorp/alicloud v1.234.0...
    - Installed hashicorp/alicloud v1.234.0 (signed by HashiCorp)
    Terraform has created a lock file .terraform.lock.hcl to record the provider
    selections it made above. Include this file in your version control repository
    so that Terraform can guarantee to make the same selections by default when
    you run "terraform init" in the future.
    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.
  3. Run the following command to create an execution plan and preview the changes:

    terraform plan
  4. Run the following command to create the CloudFlow schedule:

    terraform apply

    yes as prompted and press Enter. Wait until the command is complete. The following output indicates that the CloudFlow schedule is created:

    Plan: 9 to add, 0 to change, 0 to destroy.
    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_integer.default: Creating...
    random_integer.default: Creation complete after 0s [id=10***]
    alicloud_ram_policy.policy_example: Creating...
    alicloud_ram_role.role_example: Creating...
    alicloud_mns_queue.example: Creating...
    alicloud_event_bridge_event_bus.eventbus_example: Creating...
    alicloud_mns_queue.example: Creation complete after 0s [id=test-mns-10***]
    alicloud_ram_policy.policy_example: Creation complete after 1s [id=test-policy-10***]
    alicloud_ram_role.role_example: Creation complete after 1s [id=eb-to-fnf-r***]
    alicloud_ram_role_policy_attachment.attach_example: Creating...
    alicloud_ram_role_policy_attachment.attach_example: Creation complete after 0s [id=role:test-policy-10***:Custom:eb-to-f***]
    alicloud_fnf_flow.flow_example: Creating...
    alicloud_fnf_flow.flow_example: Creation complete after 0s [id=test-f***]
    alicloud_event_bridge_event_bus.eventbus_example: Creation complete after 2s [id=test-event***]
    alicloud_event_bridge_rule.eventrule_example: Creating...
    alicloud_event_bridge_event_source.eventsource_example: Creating...
    alicloud_event_bridge_event_source.eventsource_example: Creation complete after 0s [id=test-eventsour***]
    alicloud_event_bridge_rule.eventrule_example: Creation complete after 0s [id=test-eventbus1:test-event***]
    Apply complete! Resources: 9 added, 0 changed, 0 destroyed.

    random_integer resource generates the numeric suffix in the resource names and IDs, so these values differ each time you deploy the configuration.

Verify the results

Verify the resources that Terraform created in either of the following ways.

Run the terraform show command

Run the following command to query the details of the resources that Terraform created:

terraform show
shell@Alicloud:~/ens/fnf02$ terraform show
# alicloud_event_bridge_event_bus.eventbus_example:
resource "alicloud_event_bridge_event_bus" "eventbus_example" {
    description    = "For event_bus_description"
    event_bus_name = "test-eventbus1"
    id             = "xxx"
}
# alicloud_event_bridge_event_source.eventsource_example:
resource "alicloud_event_bridge_event_source" "eventsource_example" {
    event_bus_name        = "test-eventbus1"
    event_source_name     = "test-eventsource1"
    external_source_config = {
        "QueueName" = "test-mns-10***"
    }
    external_source_type  = "MNS"
    id                    = "test-eventsource1"
    linked_external_source = true
}
# alicloud_event_bridge_rule.eventrule_example:
resource "alicloud_event_bridge_rule" "eventrule_example" {
    event_bus_name = "test-eventbus1"
    filter_pattern = jsonencode(
        {
            source = [
                "test-eventsource1",
            ]
        }
    )
    id             = "xxx"
    rule_name      = "test-eventrule1"
    status         = "ENABLE"
    targets {
        endpoint            = "xxx"
        push_retry_strategy = "BACKOFF_RETRY"
        target_id           = "test-target1"
        type                = "acs.fnf"
        param_list {
            form         = "CONSTANT"
            resource_key = "FlowName"
        }
    }
}

Log on to the CloudFlow console

After the resources are created, log on to the CloudFlow console to check whether the workflow is created as expected.

  1. Log on to the CloudFlow console.

  2. Go to the Workflows page.

  3. Find the workflow that Terraform created and check its properties.

    The sample configuration creates a workflow with the following properties. If you changed the variable defaults, check the workflow against your own values.

  • Workflow name — test-flow, the default value of the flow_name variable.

  • Type — FDL, the value of the type attribute of the alicloud_fnf_flow resource.

  • Description — For flow_description, the default value of the flow_description variable.

Clean up resources

When you no longer need the resources that you created or manage by using Terraform, run the following command to release the resources.

Important

terraform destroy releases every resource that this configuration created, including the CloudFlow workflow, the RAM policy, the RAM role, the Simple Message Queue queue, and the EventBridge event bus, event source, and event rule.

terraform destroy

For more information about terraform destroy, see Common Terraform commands.

Complete example

The following configuration is the same one that the procedure builds step by step, provided as a single block that you can copy in full. Run in one click.

Sample code

variable "region" {
  default = "cn-hangzhou"
}
provider "alicloud" {
  region = var.region
}
# Define the name variable.
variable "name" {
  default = "test-mns"
}
# Define the policy name variable.
variable "policy_name" {
  type        = string
  description = "The name of the policy."
  default     = "test-policy"
}
# Define the role name variable.
variable "role_name" {
  type        = string
  description = "The role for eb to start execution of flow."
  default     = "eb-to-fnf-role"
}
# Define the CloudFlow name variable.
variable "flow_name" {
  type        = string
  description = "The name of the flow."
  default     = "test-flow"
}
# Define the description of the flow.
variable "flow_description" {
  default = "For flow_description"
}
# Define the event bus name variable.
variable "event_bus_name" {
  type        = string
  description = "The name of the event bus."
  default     = "test-eventbus1"
}
# Define the event bus description variable.
variable "event_bus_description" {
  default = "For event_bus_description"
}
# Define the code name variable of the event source.
variable "event_source_name" {
  type        = string
  description = "The name of the event source."
  default     = "test-eventsource1"
}
# Define the name of the event rule.
variable "event_rule_name" {
  type        = string
  description = "The name of the event rule."
  default     = "test-eventrule1"
}
# The custom ID of the event target.
variable "target_id" {
  type        = string
  description = "The ID of the target."
  default     = "test-target1"
}
# Obtain the UID of the current Alibaba Cloud account.
data "alicloud_account" "current" {
}
# Create a random number.
resource "random_integer" "default" {
  min = 10000
  max = 99999
}
# Create a RAM policy to define permissions.
resource "alicloud_ram_policy" "policy_example" {
  # The name of the RAM policy.
  policy_name     = "${var.policy_name}-${random_integer.default.result}"
  # (Optional) This parameter is used for resource destruction. Default value: false.
  force           = true
  # The document of the RAM policy.
  policy_document = <<EOF
  {
    "Statement": [
      {
        "Action": [
          "fnf:*",
          "mns:*",
          "eventbridge:*",
          "ram:*"
        ],
        "Effect": "Allow",
        "Resource": [
          "*"
        ]
      }
    ],
    "Version": "1"
  }
  EOF
}
# Create a RAM role.
resource "alicloud_ram_role" "role_example" {
  # The name of the role.
  name     = var.role_name
  # (Optional) This parameter is used for resource destruction. Default value: false.
  force    = true
  # The policy document of the role.
  document = <<EOF
  {
    "Statement": [
      {
        "Action": "sts:AssumeRole",
        "Effect": "Allow",
        "Principal": {
          "Service": [
            "fnf.aliyuncs.com"
          ]
        }
      }
    ],
    "Version": "1"
  }
  EOF
}
# Attach the created RAM policy to the RAM role to grant the corresponding permissions to the role.
resource "alicloud_ram_role_policy_attachment" "attach_example" {
  # The name of the RAM policy.
  policy_name = alicloud_ram_policy.policy_example.policy_name
  # The type of the RAM policy.
  policy_type = alicloud_ram_policy.policy_example.type
  # The name of the RAM role.
  role_name   = alicloud_ram_role.role_example.name
}
# Create a CloudFlow resource.
resource "alicloud_fnf_flow" "flow_example" {
  depends_on  = [alicloud_ram_role_policy_attachment.attach_example]
  # (Required) The definition of the flow. The definition must conform to the Flow Definition Language (FDL) syntax.
  definition  = <<EOF
  Type: StateMachine
  Name: ${var.flow_name}
  SpecVersion: v1
  StartAt: Hello World
  States:
  - Type: Pass
    Name: Hello World
    End: true
  EOF
  # The ARN of the specified RAM role that CloudFlow uses to execute the flow.
  role_arn    = alicloud_ram_role.role_example.arn
  # The description of the flow.
  description = var.flow_description
  # The name of the flow.
  name        = var.flow_name
  # The type of the flow. Valid values: FDL and DEFAULT.
  type        = "FDL"
}
# Create an event bus to receive and route events.
resource "alicloud_event_bridge_event_bus" "eventbus_example" {
  # The name of the event bus.
  event_bus_name = var.event_bus_name
  # (Optional) The description of the event bus.
  description    = var.event_bus_description
}
# Create a Simple Message Queue (formerly MNS) queue.
resource "alicloud_mns_queue" "example" {
  # The name.
  name                     = "${var.name}-${random_integer.default.result}"
  # This property defines the delay, in seconds, before a message sent to the queue can be dequeued.
  delay_seconds            = 0
  # This property specifies the maximum size, in bytes, of a message body sent to the queue.
  maximum_message_size     = 65536
  # This property defines the retention period, in seconds, of each message in the queue.
  message_retention_period = 345600
  # The visibility timeout property of the queue.
  visibility_timeout       = 30
  # The long polling time, in seconds.
  polling_wait_seconds     = 0
}
# Create an event source and link it to the Simple Message Queue (formerly MNS) queue.
resource "alicloud_event_bridge_event_source" "eventsource_example" {
  # The name of the event bus.
  event_bus_name         = alicloud_event_bridge_event_bus.eventbus_example.event_bus_name
  # The code name of the event source.
  event_source_name      = var.event_source_name
  # (Optional, Computed) Specifies whether to connect to an external data source. Default value: false.
  linked_external_source = true
  # (Optional) The type of the external data source. Valid values: RabbitMQ, RocketMQ, and MNS. Note: This field is valid only when linked_external_source is set to true.
  external_source_type   = "MNS"
  # (Optional, Map) The configuration of the external source.
  external_source_config = {
    QueueName = alicloud_mns_queue.example.name
  }
}
# Define a local variable to store the ARN of the CloudFlow workflow.
locals {
  flow_arn = format("acs:fnf:::flow/%s", var.flow_name)
}
# Create an event rule to match the events generated by the event source and route the events to the specified CloudFlow workflow.
resource "alicloud_event_bridge_rule" "eventrule_example" {
  # The name of the event bus.
  event_bus_name = alicloud_event_bridge_event_bus.eventbus_example.event_bus_name
  # The name of the event rule.
  rule_name      = var.event_rule_name
  # The pattern that matches the events of interest. The event pattern is in JSON format. The values are described as follows: stringEqual pattern and stringExpression pattern.
  filter_pattern = format("{\"source\":[\"%s\"]}", var.event_source_name)
  # The targets of the rule.
  targets {
    # The custom ID of the event target.
    target_id = var.target_id
    # The endpoint of the event target.
    endpoint  = local.flow_arn
    # The type of the event target.
    type      = "acs.fnf"
    param_list {
      resource_key = "Input"
      form         = "ORIGINAL"
    }
    param_list {
      form         = "CONSTANT"
      resource_key = "FlowName"
      value        = var.flow_name
    }
    param_list {
      form         = "CONSTANT"
      resource_key = "RoleName"
      value        = var.role_name
    }
  }
}

Troubleshooting

If terraform init times out due to reasons such as network latency and you cannot download the provider as expected, see Configure an acceleration solution for Terraform Init.