add hetzner-vm-destroy flow and document it in README
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -10,6 +10,7 @@ Aktuell enthalten:
|
|||||||
* `hetzner-vm-provision.yaml`
|
* `hetzner-vm-provision.yaml`
|
||||||
* `hetzner-server-type-location-update.yaml`
|
* `hetzner-server-type-location-update.yaml`
|
||||||
* `hetzner-server-available-list.yaml`
|
* `hetzner-server-available-list.yaml`
|
||||||
|
* `hetzner-vm-destroy.yaml`
|
||||||
* `.env.example`
|
* `.env.example`
|
||||||
|
|
||||||
Der erste Flow scannt mehrere Docker-Hosts per SSH, sucht nach Docker-Compose-Stacks und schreibt daraus eine Dropdown-Liste in den Kestra KV-Store.
|
Der erste Flow scannt mehrere Docker-Hosts per SSH, sucht nach Docker-Compose-Stacks und schreibt daraus eine Dropdown-Liste in den Kestra KV-Store.
|
||||||
@@ -208,6 +209,35 @@ TF_BACKEND_ENDPOINT
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### `hetzner-vm-destroy.yaml`
|
||||||
|
|
||||||
|
Kestra-Flow zum sicheren Löschen einer Hetzner Cloud VM via Terraform — mit Destroy-Plan, manuellem Approval-Gate und automatischer Inventar-Aktualisierung.
|
||||||
|
|
||||||
|
**Namespace:** `hetzner`
|
||||||
|
**Trigger:** Manuell (kein Schedule)
|
||||||
|
|
||||||
|
Der Flow läuft in vier Phasen:
|
||||||
|
|
||||||
|
1. **Terraform Destroy Plan** — erstellt einen Destroy-Plan für die gewählte VM; lädt den Terraform State aus dem S3-Backend
|
||||||
|
2. **Plan-Ausgabe loggen** — zeigt den vollständigen Destroy-Plan im Kestra-Log zur manuellen Prüfung
|
||||||
|
3. **Approval-Gate** — Kestra pausiert und wartet auf manuelle Freigabe
|
||||||
|
4. **Destroy / Abbruch** — bei Freigabe wird die VM gelöscht und anschließend automatisch der Subflow `hetzner-server-available-list` ausgeführt, um Inventar und KV-Store zu aktualisieren; bei Ablehnung wird abgebrochen
|
||||||
|
|
||||||
|
Die VM wird per dynamischem Dropdown aus dem KV-Key `hetzner_server_names` ausgewählt (befüllt durch `hetzner-server-available-list`).
|
||||||
|
|
||||||
|
Benötigte Kestra Secrets:
|
||||||
|
|
||||||
|
```text
|
||||||
|
HCLOUD_TOKEN
|
||||||
|
SSH_KEY_NAME
|
||||||
|
AWS_ACCESS_KEY_ID
|
||||||
|
AWS_SECRET_ACCESS_KEY
|
||||||
|
TF_BACKEND_BUCKET
|
||||||
|
TF_BACKEND_ENDPOINT
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### `.env.example`
|
### `.env.example`
|
||||||
|
|
||||||
Beispiel-Datei für benötigte Umgebungsvariablen und Secrets.
|
Beispiel-Datei für benötigte Umgebungsvariablen und Secrets.
|
||||||
|
|||||||
@@ -0,0 +1,211 @@
|
|||||||
|
id: hetzner-vm-destroy
|
||||||
|
namespace: hetzner
|
||||||
|
|
||||||
|
inputs:
|
||||||
|
- id: vm_name
|
||||||
|
type: SELECT
|
||||||
|
displayName: "VM auswählen"
|
||||||
|
description: "VM aus der aktuellen Hetzner-Serverliste auswählen. Vorher hetzner-server-available-list ausführen."
|
||||||
|
expression: "{{ kv('hetzner_server_names') }}"
|
||||||
|
|
||||||
|
tasks:
|
||||||
|
- id: terraform-destroy-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 -destroy -no-color -out=tfdestroyplan \
|
||||||
|
-var="vm_name={{ inputs.vm_name }}" \
|
||||||
|
| tee destroy-plan-output.txt
|
||||||
|
|
||||||
|
outputFiles:
|
||||||
|
- tfdestroyplan
|
||||||
|
- main.tf
|
||||||
|
- variables.tf
|
||||||
|
- destroy-plan-output.txt
|
||||||
|
|
||||||
|
- id: log-destroy-plan
|
||||||
|
type: io.kestra.plugin.core.log.Log
|
||||||
|
message: |
|
||||||
|
🧨 Terraform Destroy Plan für VM '{{ inputs.vm_name }}':
|
||||||
|
|
||||||
|
{{ read(outputs['terraform-destroy-plan'].outputFiles['destroy-plan-output.txt']) }}
|
||||||
|
|
||||||
|
──────────────────────────────────────────
|
||||||
|
→ Bitte genau prüfen und Execution fortsetzen oder abbrechen.
|
||||||
|
|
||||||
|
- id: approval-gate
|
||||||
|
type: io.kestra.plugin.core.flow.Pause
|
||||||
|
onResume:
|
||||||
|
- id: approved
|
||||||
|
type: BOOLEAN
|
||||||
|
displayName: "VM wirklich löschen?"
|
||||||
|
description: "True = VM wird gelöscht. False = Execution wird abgebrochen."
|
||||||
|
defaults: false
|
||||||
|
|
||||||
|
- id: destroy-decision
|
||||||
|
type: io.kestra.plugin.core.flow.If
|
||||||
|
condition: "{{ outputs['approval-gate'].onResume.approved == true }}"
|
||||||
|
|
||||||
|
then:
|
||||||
|
- id: terraform-destroy-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-destroy-plan'].outputFiles['main.tf'] }}"
|
||||||
|
variables.tf: "{{ outputs['terraform-destroy-plan'].outputFiles['variables.tf'] }}"
|
||||||
|
tfdestroyplan: "{{ outputs['terraform-destroy-plan'].outputFiles['tfdestroyplan'] }}"
|
||||||
|
|
||||||
|
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 apply -auto-approve -no-color tfdestroyplan
|
||||||
|
|
||||||
|
- id: update_server_inventory_after_destroy
|
||||||
|
type: io.kestra.plugin.core.flow.Subflow
|
||||||
|
namespace: hetzner
|
||||||
|
flowId: hetzner-server-available-list
|
||||||
|
wait: true
|
||||||
|
|
||||||
|
- id: log-result
|
||||||
|
type: io.kestra.plugin.core.log.Log
|
||||||
|
message: |
|
||||||
|
🗑️ VM '{{ inputs.vm_name }}' wurde erfolgreich gelöscht.
|
||||||
|
|
||||||
|
Terraform-State wurde über MinIO/S3 aktualisiert:
|
||||||
|
vms/{{ inputs.vm_name }}/terraform.tfstate
|
||||||
|
|
||||||
|
Die Hetzner-Serverliste wurde danach ebenfalls aktualisiert:
|
||||||
|
inventory/hetzner-server-available.json
|
||||||
|
inventory/hetzner-server-names.json
|
||||||
|
|
||||||
|
Kestra KV:
|
||||||
|
hetzner_server_names
|
||||||
|
|
||||||
|
else:
|
||||||
|
- id: log-aborted
|
||||||
|
type: io.kestra.plugin.core.log.Log
|
||||||
|
message: |
|
||||||
|
❌ Löschvorgang abgebrochen.
|
||||||
|
|
||||||
|
VM '{{ inputs.vm_name }}' wurde nicht gelöscht.
|
||||||
Reference in New Issue
Block a user