56a5afddc2
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
241 lines
7.2 KiB
YAML
241 lines
7.2 KiB
YAML
id: hetzner-vm-provision
|
||
namespace: hetzner
|
||
|
||
inputs:
|
||
- id: vm_name
|
||
type: STRING
|
||
displayName: "VM Name"
|
||
description: "Eindeutiger Name der VM, z.B. dev-server-01."
|
||
|
||
- id: server_type
|
||
type: SELECT
|
||
displayName: "Server-Typ"
|
||
description: "cx23 = 2 vCPU 4 GB ~4 €/Mon | cx33 = 4 vCPU 8 GB ~9 €/Mon | cpx42 = 8 vCPU 16 GB ~30 €/Mon"
|
||
values:
|
||
- cx23
|
||
- cx33
|
||
- cx43
|
||
- cx53
|
||
defaults: cx23
|
||
|
||
- id: location
|
||
type: SELECT
|
||
displayName: "Standort"
|
||
description: "Hetzner-Datacenter"
|
||
values:
|
||
- nbg1
|
||
- fsn1
|
||
- hel1
|
||
defaults: nbg1
|
||
|
||
- id: team
|
||
type: STRING
|
||
displayName: "Team"
|
||
description: "Wird als Label auf der VM gesetzt."
|
||
defaults: demo
|
||
|
||
tasks:
|
||
- id: terraform-plan
|
||
type: io.kestra.plugin.terraform.cli.TerraformCLI
|
||
containerImage: hashicorp/terraform:latest
|
||
|
||
env:
|
||
AWS_ACCESS_KEY_ID: "{{ secret('AWS_ACCESS_KEY_ID') }}"
|
||
AWS_SECRET_ACCESS_KEY: "{{ secret('AWS_SECRET_ACCESS_KEY') }}"
|
||
TF_BACKEND_BUCKET: "{{ secret('TF_BACKEND_BUCKET') }}"
|
||
TF_BACKEND_ENDPOINT: "{{ secret('TF_BACKEND_ENDPOINT') }}"
|
||
TF_VAR_hcloud_token: "{{ secret('HCLOUD_TOKEN') }}"
|
||
TF_VAR_ssh_key_name: "{{ secret('SSH_KEY_NAME') }}"
|
||
|
||
inputFiles:
|
||
main.tf: |
|
||
terraform {
|
||
required_version = ">= 1.5"
|
||
|
||
required_providers {
|
||
hcloud = {
|
||
source = "hetznercloud/hcloud"
|
||
version = "~> 1.49"
|
||
}
|
||
}
|
||
|
||
backend "s3" {}
|
||
}
|
||
|
||
provider "hcloud" {
|
||
token = var.hcloud_token
|
||
}
|
||
|
||
data "hcloud_ssh_key" "default" {
|
||
name = var.ssh_key_name
|
||
}
|
||
|
||
resource "hcloud_server" "vm" {
|
||
name = var.vm_name
|
||
image = "ubuntu-24.04"
|
||
server_type = var.server_type
|
||
location = var.location
|
||
ssh_keys = [data.hcloud_ssh_key.default.id]
|
||
|
||
public_net {
|
||
ipv4_enabled = true
|
||
ipv6_enabled = false
|
||
}
|
||
|
||
labels = {
|
||
managed_by = "terraform"
|
||
team = var.team
|
||
}
|
||
}
|
||
|
||
output "vm_ipv4" {
|
||
value = hcloud_server.vm.ipv4_address
|
||
}
|
||
|
||
variables.tf: |
|
||
variable "hcloud_token" {
|
||
type = string
|
||
sensitive = true
|
||
}
|
||
|
||
variable "ssh_key_name" {
|
||
type = string
|
||
}
|
||
|
||
variable "vm_name" {
|
||
type = string
|
||
}
|
||
|
||
variable "server_type" {
|
||
type = string
|
||
default = "cx23"
|
||
}
|
||
|
||
variable "location" {
|
||
type = string
|
||
default = "nbg1"
|
||
}
|
||
|
||
variable "team" {
|
||
type = string
|
||
default = "demo"
|
||
}
|
||
|
||
beforeCommands:
|
||
- |
|
||
terraform init -reconfigure \
|
||
-backend-config="bucket=$TF_BACKEND_BUCKET" \
|
||
-backend-config="endpoints={s3=\"$TF_BACKEND_ENDPOINT\"}" \
|
||
-backend-config="key=vms/{{ inputs.vm_name }}/terraform.tfstate" \
|
||
-backend-config="region=us-east-1" \
|
||
-backend-config="skip_requesting_account_id=true" \
|
||
-backend-config="skip_credentials_validation=true" \
|
||
-backend-config="skip_metadata_api_check=true" \
|
||
-backend-config="skip_region_validation=true" \
|
||
-backend-config="use_path_style=true"
|
||
|
||
commands:
|
||
- |
|
||
set -eo pipefail
|
||
|
||
terraform plan -no-color -out=tfplan \
|
||
-var="vm_name={{ inputs.vm_name }}" \
|
||
-var="server_type={{ inputs.server_type }}" \
|
||
-var="location={{ inputs.location }}" \
|
||
-var="team={{ inputs.team }}" \
|
||
| tee plan-output.txt
|
||
|
||
outputFiles:
|
||
- tfplan
|
||
- main.tf
|
||
- variables.tf
|
||
- plan-output.txt
|
||
|
||
- id: llm-summary
|
||
type: io.kestra.plugin.openai.ChatCompletion
|
||
apiKey: "{{ secret('OPENAI_API_KEY') }}"
|
||
model: gpt-4o-mini
|
||
messages:
|
||
- role: system
|
||
content: "Du bist ein DevOps-Assistent. Fasse den folgenden Terraform Plan auf Deutsch in 3-5 Sätzen zusammen. Erkläre, was erstellt oder geändert wird und ob der Plan sicher aussieht."
|
||
- role: user
|
||
content: "{{ read(outputs['terraform-plan'].outputFiles['plan-output.txt']) }}"
|
||
|
||
- id: log-summary
|
||
type: io.kestra.plugin.core.log.Log
|
||
message: |
|
||
📋 KI-Analyse des Terraform Plans:
|
||
|
||
{{ outputs['llm-summary'].choices[0].message.content }}
|
||
|
||
──────────────────────────────────────────
|
||
→ Bitte prüfen und Execution fortsetzen oder ablehnen.
|
||
|
||
- id: approval-gate
|
||
type: io.kestra.plugin.core.flow.Pause
|
||
onResume:
|
||
- id: approved
|
||
type: BOOLEAN
|
||
displayName: "Deployment genehmigen?"
|
||
description: "Ja = VM wird erstellt. Nein = Execution wird abgebrochen."
|
||
defaults: false
|
||
|
||
- id: deploy-decision
|
||
type: io.kestra.plugin.core.flow.If
|
||
condition: "{{ outputs['approval-gate'].onResume.approved == true }}"
|
||
|
||
then:
|
||
- id: terraform-apply
|
||
type: io.kestra.plugin.terraform.cli.TerraformCLI
|
||
containerImage: hashicorp/terraform:latest
|
||
|
||
env:
|
||
AWS_ACCESS_KEY_ID: "{{ secret('AWS_ACCESS_KEY_ID') }}"
|
||
AWS_SECRET_ACCESS_KEY: "{{ secret('AWS_SECRET_ACCESS_KEY') }}"
|
||
TF_BACKEND_BUCKET: "{{ secret('TF_BACKEND_BUCKET') }}"
|
||
TF_BACKEND_ENDPOINT: "{{ secret('TF_BACKEND_ENDPOINT') }}"
|
||
TF_VAR_hcloud_token: "{{ secret('HCLOUD_TOKEN') }}"
|
||
TF_VAR_ssh_key_name: "{{ secret('SSH_KEY_NAME') }}"
|
||
|
||
inputFiles:
|
||
main.tf: "{{ outputs['terraform-plan'].outputFiles['main.tf'] }}"
|
||
variables.tf: "{{ outputs['terraform-plan'].outputFiles['variables.tf'] }}"
|
||
tfplan: "{{ outputs['terraform-plan'].outputFiles['tfplan'] }}"
|
||
|
||
beforeCommands:
|
||
- |
|
||
terraform init -reconfigure \
|
||
-backend-config="bucket=$TF_BACKEND_BUCKET" \
|
||
-backend-config="endpoints={s3=\"$TF_BACKEND_ENDPOINT\"}" \
|
||
-backend-config="key=vms/{{ inputs.vm_name }}/terraform.tfstate" \
|
||
-backend-config="region=us-east-1" \
|
||
-backend-config="skip_requesting_account_id=true" \
|
||
-backend-config="skip_credentials_validation=true" \
|
||
-backend-config="skip_metadata_api_check=true" \
|
||
-backend-config="skip_region_validation=true" \
|
||
-backend-config="use_path_style=true"
|
||
|
||
commands:
|
||
- terraform apply -auto-approve -no-color tfplan
|
||
- terraform output -raw vm_ipv4 | tr -d '\n' > vm_ipv4.txt
|
||
|
||
outputFiles:
|
||
- vm_ipv4.txt
|
||
|
||
- id: log-result
|
||
type: io.kestra.plugin.core.log.Log
|
||
message: |
|
||
✅ VM erfolgreich bereitgestellt!
|
||
|
||
Name: {{ inputs.vm_name }}
|
||
Typ: {{ inputs.server_type }} @ {{ inputs.location }}
|
||
Team: {{ inputs.team }}
|
||
IPv4: {{ read(outputs['terraform-apply'].outputFiles['vm_ipv4.txt']) }}
|
||
|
||
SSH: ssh root@{{ read(outputs['terraform-apply'].outputFiles['vm_ipv4.txt']) }}
|
||
|
||
else:
|
||
- id: log-aborted
|
||
type: io.kestra.plugin.core.log.Log
|
||
message: "❌ Deployment abgebrochen – Approval wurde verweigert."
|