Blueprints

Create a GCP VM with IPAM and Terraform, then notify Slack

Source

yaml
id: create-vm-gcp-terraform
namespace: solutions.infra
description: >
  Create a VM on Google Cloud by generating a hostname, reserving an IP from an
  external IPAM, provisioning the instance via Terraform, and notifying Slack
  with the resulting details.

inputs:
  - id: cloudProvider
    displayName: Cloud Provider
    type: SELECT
    values:
      - AWS
      - GCP
      - Azure
      - VCF
    defaults: GCP

  - id: billingAccount
    type: SELECT
    displayName: Billing Account
    description: |
      This is typically your
      Account ID for AWS
      Project ID for GCP
      Subscription ID for Azure
    values:
      - sandbox
      - dev
    defaults: sandbox
    dependsOn:
      inputs:
        - cloudProvider
      condition: "{{ inputs.cloudProvider == 'GCP' }}"

  - id: region
    type: SELECT
    displayName: Region
    values:
      - us-central1
      - us-east1
    dependsOn:
      inputs:
        - billingAccount
      condition: "{{ inputs.billingAccount == 'kestra-sandbox' }}"
    defaults: us-central1

  - id: zone
    type: SELECT
    displayName: Zone
    values:
      - us-central1-a
      - us-central1-b
    dependsOn:
      inputs:
        - region
      condition: "{{ inputs.region == 'us-central1' }}"
    defaults: us-central1-a

  - id: os
    displayName: Operating System
    type: SELECT
    values:
      - Windows
      - Linux
    defaults: Linux

  - id: osFlavor
    displayName: OS Version
    type: SELECT
    values:
      - Ubuntu 24.04
      - Ubuntu 20.04
      - ubuntu-minimal-2210-kinetic-amd64-v20230126
    defaults: ubuntu-minimal-2210-kinetic-amd64-v20230126
    dependsOn:
      inputs:
        - os
      condition: "{{ inputs.os == 'Linux' }}"

  - id: vmSize
    displayName: VM Size
    type: SELECT
    values:
      - Small
      - Medium
      - Large
      - n1-standard-1
    description: |
      Small   - 2 vCPUs 4 GB RAM
      Medium  - 4 vCPUs 8 GB RAM
      Large   - 8 vCPUs 16 GB RAM
    defaults: n1-standard-1

  - id: network
    displayName: Network
    type: SELECT
    values:
      - demo-vpc
      - vpc-02
    defaults: demo-vpc

  - id: subnet
    displayName: Subnet
    type: SELECT
    values:
      - demo-vpc
      - subnet-02
    defaults: demo-vpc

  - id: vmType
    type: SELECT
    displayName: Type of VM
    values:
      - Application
      - Database
      - Server
    defaults: Application

  - id: env
    type: SELECT
    displayName: Environment
    values:
      - Development
      - Staging
      - Production
    defaults: Development

tasks:
  - id: generateCustomHostname
    type: io.kestra.plugin.scripts.python.Script
    beforeCommands:
      - pip install kestra
    script: |
      from kestra import Kestra

      try:
          cloud_code = "{{ inputs.cloudProvider | slice(0, 3) }}"
          env_code = "{{ inputs.env | slice(0, 3) }}"
          os_code = "{{ inputs.os | slice(0, 3) }}"
          vm_code = "{{ inputs.vmType | slice(0, 3) }}"
          index_code = {{ kv('getVMIndex', errorOnMissing = false) ?? 1 }}

          vm_name = f"{cloud_code}{env_code}{os_code}{vm_code}{index_code}"

          Kestra.outputs({'customHostname': vm_name.lower(), 'index_code': index_code})

      except KeyError as e:
          print(f"Invalid value for input: {e}")
      except Exception as e:
          print(str(e))

  - id: getIPAddress
    type: io.kestra.plugin.scripts.python.Script
    beforeCommands:
      - pip install requests kestra
    script: |
      from kestra import Kestra
      import requests

      IPAM_BASE_URL = "{{ kv('ipamAddress', errorOnMissing = false) ?? 'localhost' }}/api/dev-ipam"
      endpoint = f"subnets/{{inputs.subnet}}/first_free"

      response = requests.get(f"{IPAM_BASE_URL}/{endpoint}", verify=False)
      print(response.status_code)
      ip_address = response.json().get("ipAddress")
      Kestra.outputs({'status': response.status_code, 'ipAddress': ip_address})

  - id: createVM
    type: io.kestra.plugin.terraform.cli.TerraformCLI
    inputFiles:
      main.tf: |
        resource "google_compute_instance" "default" {
          name         = "{{ outputs.generateCustomHostname.vars.customHostname }}"
          machine_type = "{{ inputs.vmSize }}"
          zone         = "{{ inputs.zone }}"
          project      = "{{ inputs.billingAccount }}"

          boot_disk {
            initialize_params {
              image = "{{ inputs.osFlavor }}"
            }
          }

          network_interface {
            network            = "{{ inputs.network }}"
            subnetwork         = "{{ inputs.subnet }}"
            subnetwork_project = "{{ inputs.billingAccount }}"
            access_config {}
            network_ip = "{{ outputs.getIPAddress.vars.ipAddress }}"
          }

          metadata = {
            enable-oslogin = false
            "ssh-keys" = <<EOT
              root:{{ secret('SSH_PUB_KEY') }}
            EOT
          }
        }

        output "external_ip" {
          value       = google_compute_instance.default.network_interface[0].access_config[0].nat_ip
          description = "The external IP address of the VM"
        }
      gcp-svc-account.json: "{{ secret('GCP_SVC_ACCOUNT') }}"
    env:
      GOOGLE_APPLICATION_CREDENTIALS: gcp-svc-account.json
    beforeCommands:
      - terraform init
    commands:
      - terraform plan
      - terraform apply -auto-approve -json
      - terraform output -raw external_ip > external_ip.txt
    allowFailure: false
    outputFiles:
      - "*.txt"

  - id: notifyChannel
    type: io.kestra.plugin.notifications.slack.SlackIncomingWebhook
    url: "{{ secret(namespace='system', key='SLACK_WEBHOOK') }}"
    messageText: |
      {
        "text": "{{ outputs.generateCustomHostname.vars.customHostname }} created successfully with the following properties\n ExternalIPAddress: {{ read(outputs.createVM.outputFiles['external_ip.txt']) }}\n DefaultUser: {{ secret('SSH_VM_USER') }}\n Cloud: {{ inputs.cloudProvider }}\n Account: {{ inputs.billingAccount }}\n Region: {{ inputs.region }}\n Zone: {{ inputs.zone }}\n Network: {{ inputs.network }}\n Subnet: {{ inputs.subnet }}\n Size: {{ inputs.vmSize }}\n OS: {{ inputs.os }}\n Version: {{ inputs.osFlavor }}\n Environment: {{ inputs.env }}\n VMType: {{ inputs.vmType }}\n IPAddress: {{ outputs.getIPAddress['vars']['ipAddress'] }}\n"
      }

  - id: incrementindex
    type: io.kestra.plugin.core.kv.Set
    key: getVMIndex
    kvType: NUMBER
    value: "{{ outputs.generateCustomHostname.vars.index_code + 1 }}"
    allowFailure: true

outputs:
  - id: externalIPAddress
    type: STRING
    value: "{{ read(outputs.createVM.outputFiles['external_ip.txt']) ?? 'Failed' }}"

About this blueprint

Infrastructure

This flow generates a hostname, reserves an IP from an external IPAM, and provisions a Google Compute Engine VM via Terraform using inline files. It posts a Slack notification with the resulting external IP and configuration details, and increments a KV index for the next VM name.

Script

Terraform CLI

Slack Incoming Webhook

Set

More Related Blueprints

New to Kestra?

Use blueprints to kickstart your first workflows.

Get started with Kestra