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.
More Related Blueprints