diff --git a/.github/workflows/default-ci.yaml b/.github/workflows/default-ci.yaml index 6a63d14..d4a6b83 100644 --- a/.github/workflows/default-ci.yaml +++ b/.github/workflows/default-ci.yaml @@ -21,16 +21,6 @@ on: branches: ["main"] jobs: - secret-scan: - name: "Secret Scanner (TruffleHog)" - runs-on: ${{ github.server_url == 'https://github.com' && 'ubuntu-latest' || 'stackit-docker' }} - steps: - - name: Checkout code - uses: actions/checkout@v7 - - - name: TruffleHog Scan - uses: edplato/trufflehog-actions-scan@master - todo-check: name: "Check for Open TODOs" runs-on: ${{ github.server_url == 'https://github.com' && 'ubuntu-latest' || 'stackit-ubuntu-22' }} diff --git a/.gitignore b/.gitignore index bea598b..ea15fc1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ .### Terraform template # Local .terraform directories -**/.terraform/* +**/.terraform/** # .tfstate files *.tfstate @@ -72,3 +72,8 @@ keys ### K8s .kubeconfig /examples/telemetry-router-hub-spoke-setup/scripts/downloads/ + +*.DS_Store + +# ignore backend.conf files, but keep backend.conf.example +*backend.conf diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 880d108..9f9effb 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -30,11 +30,6 @@ repos: args: [--allow-multiple-documents] - id: check-added-large-files - - repo: https://github.com/gitleaks/gitleaks - rev: v8.18.2 - hooks: - - id: gitleaks - - repo: https://github.com/antonbabenko/pre-commit-terraform rev: v1.88.0 hooks: @@ -58,13 +53,18 @@ repos: - id: prettier types_or: [javascript, yaml, json, markdown] + - repo: https://github.com/gitleaks/gitleaks + rev: v8.24.2 + hooks: + - id: gitleaks + - repo: local hooks: # Requires `addlicense` to be installed locally (go install github.com/google/addlicense@latest) - id: addlicense name: Add License Headers - description: Ensures all files have the Apache 2.0 license header - entry: addlicense -c "Schwarz Digits Cloud GmbH & Co. KG" -l apache + description: Ensures all files have the Apache 2.0 license header (skips files starting with #cloud-config) + entry: bash -c 'for f in "$@"; do head -n 1 "$f" | grep -q "^#cloud-config" || addlicense -c "Schwarz Digits Cloud GmbH & Co. KG" -l apache "$f"; done' -- language: system types_or: [terraform, python, go, javascript, yaml, json, html, css] pass_filenames: true diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0bd64ec..6282ab3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -59,7 +59,7 @@ To maintain a clean and secure codebase, we enforce a strict CI pipeline on all ``` - **Terraform file naming:** All `.tf` files in examples **must** be prefixed with exactly 3 digits to enforce consistent ordering (e.g., `010-provider.tf`, `020-variables.tf`, `030-resources.tf`, `100-outputs.tf`). Files inside `modules/` directories are exempt from this rule. This check is enforced automatically by pre-commit. -- **Scan for Secrets:** Never commit credentials. We use `trufflehog` in the CI pipeline. Ensure you have no hardcoded tokens or passwords in your code. +- **Scan for Secrets:** Never commit credentials. We use `gitleaks` in the CI pipeline. Ensure you have no hardcoded tokens or passwords in your code. ### Repository structure diff --git a/GOVERNANCE.md b/GOVERNANCE.md index 8b1c7fc..aa46534 100644 --- a/GOVERNANCE.md +++ b/GOVERNANCE.md @@ -30,7 +30,7 @@ To ensure high standards and security, we follow a strict contribution process: - **Automated Validation:** Every Pull Request must pass the CI pipeline, which includes: - Linting and formatting checks. - License header verification (Apache 2.0). - - Secret scanning (Trufflehog). + - Secret scanning (GitLeaks). - **Best Effort Policy:** While we strive for quality, the content is provided "as-is." Use in production environments requires independent validation by the user. ## 4. Mirroring Process diff --git a/examples/iaas-cross-az-layer4-loadbalancer/apache-debug-user.yaml b/examples/iaas-cross-az-layer4-loadbalancer/apache-debug-user.yaml index 74c4065..c44cda9 100644 --- a/examples/iaas-cross-az-layer4-loadbalancer/apache-debug-user.yaml +++ b/examples/iaas-cross-az-layer4-loadbalancer/apache-debug-user.yaml @@ -1,17 +1,3 @@ -# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - #cloud-config users: - name: debug diff --git a/examples/iaas-cross-az-layer7-loadbalancer-waf/apache-debug-user.yaml b/examples/iaas-cross-az-layer7-loadbalancer-waf/apache-debug-user.yaml index 74c4065..c44cda9 100644 --- a/examples/iaas-cross-az-layer7-loadbalancer-waf/apache-debug-user.yaml +++ b/examples/iaas-cross-az-layer7-loadbalancer-waf/apache-debug-user.yaml @@ -1,17 +1,3 @@ -# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - #cloud-config users: - name: debug diff --git a/examples/iaas-ha-vrrp/cloud-init.yaml b/examples/iaas-ha-vrrp/cloud-init.yaml index 8224582..596b229 100644 --- a/examples/iaas-ha-vrrp/cloud-init.yaml +++ b/examples/iaas-ha-vrrp/cloud-init.yaml @@ -1,17 +1,3 @@ -# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - #cloud-config # Update apt database on first boot (run 'apt-get update'). # Note, if packages are given, or package_upgrade is true, then diff --git a/examples/iaas-image-upload/.terraform-version b/examples/iaas-image-upload/.terraform-version new file mode 100644 index 0000000..22708fe --- /dev/null +++ b/examples/iaas-image-upload/.terraform-version @@ -0,0 +1 @@ +v1.5.7 diff --git a/examples/iaas-image-upload/.terraform.lock.hcl b/examples/iaas-image-upload/.terraform.lock.hcl new file mode 100644 index 0000000..ac34bd3 --- /dev/null +++ b/examples/iaas-image-upload/.terraform.lock.hcl @@ -0,0 +1,24 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/stackitcloud/stackit" { + version = "0.99.0" + constraints = ">= 0.99.0" + hashes = [ + "h1:a9z0j1z/8GmGjz+VygIhgyBbMqxx7jlXGqCvWBDD1NY=", + "zh:0dde99e7b343fa01f8eefc378171fb8621bedb20f59157d6cc8e3d46c738105f", + "zh:396c0392b9ef5ec7f8613c29a64e183545cc16dda0ceb876393fc003dba71c73", + "zh:40d86a1fb1c9ed4579583acb8ecc219edca44f9ee5221bfdcbc1bee2ce6654e7", + "zh:4ccbbecc3575737d87195ad13448d06071be9925760a2da5b7e5e8b91517f876", + "zh:506d786647c4566a82487fc3ffe0792f37a63ec8d6b54821aa3c7485e5ed6760", + "zh:848f638c500f1928f8593ae189472add1a0871c1e056d7df06871652ddee3409", + "zh:9ed739aec2c60cdfae3a33e4f349fa630fd0fd0ab50fcec5745774d42a6d6e70", + "zh:c0ac883dd73bd886e419d912c28ec29bb90a611b023cf4ae1b0534945cce1694", + "zh:df28663578694b25453b9d0a1cd7633a0f7fb1c113870cd3c133e9dc05d35946", + "zh:eaacb4a4512f41d44e46f82f042a19ab96c9d90d470890e2fd82c6cafb33bf0e", + "zh:ef9dd9b10571804f3a4dd6062405d0e473df270d75f05f897901c54d7d6c3d9d", + "zh:f40add9cd4fd4a7cda53f4a418c5f47a220b5ba5c4fc2377f60b1e16368f87d9", + "zh:f65deb30c1e3e8018a888d1aed56e895ea1e26b880f22a5772771e9836c9b5a4", + "zh:f8d14feddfd9d785d3ee6469937234a631998758ea5e8c16ecf61cdb94b07564", + ] +} diff --git a/examples/iaas-image-upload/000-provider.tf b/examples/iaas-image-upload/000-provider.tf new file mode 100644 index 0000000..4a26c40 --- /dev/null +++ b/examples/iaas-image-upload/000-provider.tf @@ -0,0 +1,28 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +terraform { + required_version = ">= 1.5.7" + required_providers { + stackit = { + source = "stackitcloud/stackit" + version = ">= 0.99.0" + } + } +} + +provider "stackit" { + default_region = var.stackit_region + service_account_key_path = var.stackit_service_account_key_path +} diff --git a/examples/iaas-image-upload/010-variables.tf b/examples/iaas-image-upload/010-variables.tf new file mode 100644 index 0000000..3e2d6ba --- /dev/null +++ b/examples/iaas-image-upload/010-variables.tf @@ -0,0 +1,159 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# ─── Provider ───────────────────────────────────────────────────────────────── + +variable "stackit_region" { + description = "STACKIT region, e.g. eu01" + type = string + default = "eu01" +} + +variable "stackit_service_account_key_path" { + description = "Path to the STACKIT service account key JSON file" + type = string + default = "keys/sa-key.json" +} + +# ─── Project ────────────────────────────────────────────────────────────────── + +variable "project_id" { + description = "STACKIT project ID to which the image is uploaded — find it in the Portal or via: stackit project list" + type = string +} + +# ─── Image ──────────────────────────────────────────────────────────────────── + +variable "image_name" { + description = "Name of the custom image as it will appear in STACKIT" + type = string +} + +variable "image_file_path" { + description = "Local path to the image file to upload, e.g. ./images/custom-image.qcow2 — the file must exist before running terraform apply" + type = string +} + +variable "disk_format" { + description = "Disk format of the image. Supported values: qcow2, raw, iso" + type = string + default = "qcow2" + + validation { + condition = contains(["qcow2", "raw", "iso"], var.disk_format) + error_message = "disk_format must be one of: qcow2, raw, iso." + } +} + +variable "min_disk_size" { + description = "Minimum disk size required to boot instances from this image, in GB" + type = number + default = 20 + + validation { + condition = var.min_disk_size >= 1 + error_message = "min_disk_size must be at least 1 GB." + } +} + +variable "min_ram" { + description = "Minimum RAM required to boot instances from this image, in MB" + type = number + default = 2048 + + validation { + condition = var.min_ram >= 1 + error_message = "min_ram must be at least 1 MB." + } +} + +# ─── Image Config ───────────────────────────────────────────────────────────── + +variable "uefi" { + description = "Enable UEFI boot for instances created from this image. Set to false for BIOS-only images." + type = bool + default = true +} + +variable "secure_boot" { + description = "Enable Secure Boot for instances created from this image. Requires UEFI (uefi = true)." + type = bool + default = false +} + +# ─── Server ─────────────────────────────────────────────────────────────────── + +variable "server_name" { + description = "Name of the server to create from the uploaded image" + type = string + default = "custom-image-server" +} + +variable "machine_type" { + description = "STACKIT machine type for the server — list available: stackit server machine-type list --project-id " + type = string + default = "g1.1" +} + +variable "availability_zone" { + description = "Availability zone for the server, e.g. eu01-1, eu01-2, eu01-3" + type = string + default = "eu01-1" +} + +variable "boot_volume_size_gb" { + description = "Boot volume size in GB — must be >= min_disk_size of the image" + type = number + default = 20 + + validation { + condition = var.boot_volume_size_gb >= 1 + error_message = "boot_volume_size_gb must be at least 1 GB." + } +} + +variable "network_cidr" { + description = "IPv4 CIDR block for the server's private network" + type = string + default = "10.10.0.0/24" + + validation { + condition = can(cidrnetmask(var.network_cidr)) + error_message = "network_cidr must be a valid CIDR, e.g. 10.10.0.0/24." + } +} + +variable "keypair_name" { + description = "Name of the SSH key pair to register in STACKIT" + type = string + default = "custom-image-key" +} + +variable "ssh_public_key" { + description = "SSH public key string (ssh-ed25519 AAAA... or ssh-rsa AAAA...) — only required when deploy_server = true" + type = string + sensitive = true + default = "" +} + +# ─── Labels ─────────────────────────────────────────────────────────────────── + +variable "labels" { + description = "Key-value labels to attach to the image resource" + type = map(string) + default = { + managed-by = "terraform" + example = "image-upload" + } +} diff --git a/examples/iaas-image-upload/020-image.tf b/examples/iaas-image-upload/020-image.tf new file mode 100644 index 0000000..f270f91 --- /dev/null +++ b/examples/iaas-image-upload/020-image.tf @@ -0,0 +1,30 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +resource "stackit_image" "custom_image" { + project_id = var.project_id + name = var.image_name + disk_format = var.disk_format + local_file_path = var.image_file_path + + min_disk_size = var.min_disk_size + min_ram = var.min_ram + + labels = var.labels + + config = { + uefi = var.uefi + secure_boot = var.secure_boot + } +} diff --git a/examples/iaas-image-upload/030-outputs.tf b/examples/iaas-image-upload/030-outputs.tf new file mode 100644 index 0000000..c2ad9cb --- /dev/null +++ b/examples/iaas-image-upload/030-outputs.tf @@ -0,0 +1,43 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +output "image_id" { + description = "ID of the uploaded custom image — use this UUID when creating servers or volumes" + value = stackit_image.custom_image.image_id +} + +output "image_name" { + description = "Name of the uploaded custom image" + value = stackit_image.custom_image.name +} + +output "image_scope" { + description = "Scope of the image (private or public)" + value = stackit_image.custom_image.scope +} + +output "checksum_algorithm" { + description = "Algorithm used for the image checksum" + value = stackit_image.custom_image.checksum.algorithm +} + +output "checksum_digest" { + description = "Checksum digest of the uploaded image — verify this against your local file" + value = stackit_image.custom_image.checksum.digest +} + +output "server_id" { + description = "ID of the server created from the custom image" + value = stackit_server.from_custom_image.server_id +} diff --git a/examples/iaas-image-upload/040-server.tf b/examples/iaas-image-upload/040-server.tf new file mode 100644 index 0000000..a24f5b8 --- /dev/null +++ b/examples/iaas-image-upload/040-server.tf @@ -0,0 +1,51 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Example: boot a server from the uploaded custom image. +# The image_id is referenced directly from the stackit_image resource above. + +resource "stackit_network" "main" { + project_id = var.project_id + name = "${var.server_name}-network" + ipv4_prefix_length = tonumber(split("/", var.network_cidr)[1]) +} + +resource "stackit_network_interface" "server" { + project_id = var.project_id + network_id = stackit_network.main.network_id + name = "${var.server_name}-nic" +} + +resource "stackit_key_pair" "server" { + name = var.keypair_name + public_key = chomp(var.ssh_public_key) +} + +resource "stackit_server" "from_custom_image" { + project_id = var.project_id + name = var.server_name + machine_type = var.machine_type + availability_zone = var.availability_zone + keypair_name = stackit_key_pair.server.name + + boot_volume = { + source_type = "image" + source_id = stackit_image.custom_image.image_id + size = var.boot_volume_size_gb + } + + network_interfaces = [stackit_network_interface.server.network_interface_id] + + labels = var.labels +} diff --git a/examples/iaas-image-upload/MAINTAINERS.md b/examples/iaas-image-upload/MAINTAINERS.md new file mode 100644 index 0000000..345a653 --- /dev/null +++ b/examples/iaas-image-upload/MAINTAINERS.md @@ -0,0 +1,9 @@ +# Maintainers + +General maintainers: + +- Sven Schmidt (sven.schmidt@digits.schwarz) + +This example is actively maintained. The owner is responsible for reviewing and updating dependencies and functionalities on a monthly basis. +For questions, issues, or feature requests, please email general maintainers. +Please include the BP name and version in your request. We will track your request as an issue. diff --git a/examples/iaas-image-upload/README.md b/examples/iaas-image-upload/README.md new file mode 100644 index 0000000..1bd0e10 --- /dev/null +++ b/examples/iaas-image-upload/README.md @@ -0,0 +1,272 @@ +# iaas-image-upload + +Upload a custom VM image to STACKIT using Terraform. + +This example provisions a single `stackit_image` resource from a local image file. It covers the minimal configuration needed to make a custom image available in a STACKIT project, including disk format, boot requirements, and UEFI/Secure Boot settings. + +--- + +## Architecture + +```mermaid +sequenceDiagram + participant User + participant FS as Local Filesystem + participant TF as Terraform / STACKIT API + participant IMG as STACKIT Image Service + participant SRV as STACKIT Compute + + Note over User,SRV: terraform apply + + User->>FS: read image file (local_file_path) + FS-->>TF: binary image data + + User->>TF: create image metadata (name, format, min_disk, min_ram, config) + TF-->>IMG: POST /v1/projects/{project_id}/images + IMG-->>TF: image_id · status: uploading + + TF-->>IMG: PUT upload binary image data + IMG-->>IMG: compute checksum · validate format + IMG-->>TF: status: active · checksum_digest + + TF-->>User: ✅ image_id · checksum_digest + + Note over User,SRV: 04-server.tf + + User->>TF: create Key Pair + Server (source_id = image_id) + TF-->>SRV: POST /v1/projects/{project_id}/servers + SRV-->>TF: server_id + + TF-->>User: ✅ server_id +``` + +``` +STACKIT Project +├── Image: custom-image-v1 (scope: private) +│ ├── Format: qcow2 +│ ├── Min disk: 20 GB +│ ├── Min RAM: 2048 MB +│ └── Config: uefi=true · secure_boot=false +│ +└── Server: custom-image-server + ├── Machine: g1.1 + ├── Zone: eu01-1 + └── Boot: source_type=image · source_id= +``` + +--- + +## Overview + +| Component | Description | +| ------------ | -------------------------------------------------------- | +| Image upload | Uploads a local image file to the STACKIT Image Service | +| Boot config | Configures UEFI and Secure Boot flags via `config` block | +| Labels | Attaches key-value labels for resource management | + +| In this example | Not in this example | +| ----------------------------- | ----------------------------- | +| Upload a custom image | Image sharing across projects | +| UEFI and Secure Boot settings | Auto-scaling / multiple VMs | +| Boot a server from the image | Storage encryption | +| Image labels | DNS / Load Balancer | + +--- + +## Prerequisites + +| Tool | Version | +| --------------- | -------------- | +| Terraform | >= 1.5.7 | +| STACKIT CLI | latest | +| STACKIT account | Project access | + +### Required STACKIT permissions + +The service account used by Terraform must have the following roles assigned in the target project: + +| Service | Role | Required for | +| --------------- | -------- | ----------------------------------- | +| Compute / Image | `editor` | Upload and manage custom images | +| Compute | `editor` | Create servers, networks, key pairs | + +Assign roles via the STACKIT Portal (Project → Access → Service Accounts) or the CLI: + +```bash +stackit project role assign \ + --project-id \ + --role compute.editor \ + --service-account-email +``` + +### Image file + +The image file must exist locally before running `terraform apply`. It is **not** included in this repository and must never be committed. + +Place your image file in the `images/` directory: + +``` +images/ +└── custom-image.qcow2 ← your local image file (gitignored) +``` + +Supported formats: `qcow2`, `raw`, `iso` + +The most common format for Linux-based virtual machine images is `qcow2`. + +### Service account + +Create a service account and download its key: + +```bash +stackit iam service-account create \ + --project-id \ + --name "tf-image-upload-sa" + +mkdir -p keys +stackit iam service-account key create \ + --project-id \ + --service-account-email \ + --output-format json > keys/sa-key.json +``` + +--- + +## Deployment + +### 1. Place the image file + +```bash +cp /path/to/your/image.qcow2 images/custom-image.qcow2 +``` + +### 2. Configure variables + +```bash +cp examples/terraform.tfvars.example terraform.tfvars +# Fill in: project_id, image_name, image_file_path +``` + +Find your project ID: + +```bash +stackit project list +``` + +### 3. Deploy + +```bash +terraform init +terraform plan +terraform apply +``` + +Duration: depends on image size and upload speed. A 2 GB image typically takes 1–3 minutes. + +### 4. Outputs + +```bash +terraform output +``` + +--- + +## Validation + +After a successful `terraform apply`, verify the image in STACKIT: + +```bash +# List images in your project +stackit image list --project-id + +# Show details for the uploaded image +stackit image show --project-id --image-id $(terraform output -raw image_id) +``` + +The `checksum_digest` output can be used to verify the uploaded image matches your local file: + +```bash +# qcow2 image checksum (SHA-256) +sha256sum images/custom-image.qcow2 +terraform output checksum_digest +``` + +--- + +## File Structure + +``` +iaas-image-upload/ +├── .terraform-version # Terraform version pin (v1.5.7) +├── .gitignore +├── 000-provider.tf # stackitcloud/stackit provider +├── 010-variables.tf # All variables with descriptions and defaults +├── 020-image.tf # stackit_image resource +├── 030-outputs.tf # Image ID, name, scope, checksum, server outputs +├── 040-server.tf # Optional: server + network from the uploaded image +├── examples/ +│ └── terraform.tfvars.example # Example variable values (safe to commit) +├── images/ # Place your image file here (gitignored) +│ └── .gitkeep +└── keys/ # SA key JSON — gitignored +``` + +--- + +## UEFI and Secure Boot + +| Variable | Default | Description | +| ------------- | ------- | -------------------------------------------------------- | +| `uefi` | `true` | Enables UEFI boot; set to `false` for legacy BIOS images | +| `secure_boot` | `false` | Enables Secure Boot; requires `uefi = true` | + +Most modern Linux distributions support UEFI. If your image was built for BIOS boot only, set `uefi = false` and `secure_boot = false`. + +Secure Boot requires a signed bootloader. Enable it only if your image explicitly supports it. + +--- + +## Deploy a server from the uploaded image + +`04-server.tf` creates a server that boots directly from the uploaded image. +The `source_id` is wired to `stackit_image.custom_image.image_id` — no manual copy-paste of the image UUID needed. + +Provide your SSH public key in `terraform.tfvars`: + +```hcl +ssh_public_key = "ssh-ed25519 AAAA... your-key-comment" +``` + +Extend `04-server.tf` with `stackit_network` and `stackit_network_interface` resources if network connectivity is required. + +The default SSH user depends on the operating system of your image (e.g. `debian`, `ubuntu`, `root`). + +--- + +## Security + +| File | Git status | Contains | +| ------------------ | ---------- | ---------------------------- | +| `terraform.tfvars` | gitignored | Project ID, sensitive config | +| `keys/` | gitignored | Service account JSON key | +| `images/` | gitignored | Local image files | + +Never commit image files, service account keys, or `terraform.tfvars` to the repository. + +--- + +## Cleanup + +```bash +terraform destroy +``` + +Removes the uploaded image from STACKIT. This does not affect any running instances that were created from the image. + +--- + +## References + +- [STACKIT Terraform Provider — stackit_image](https://registry.terraform.io/providers/stackitcloud/stackit/latest/docs/resources/image) +- [STACKIT Developer Documentation](https://docs.stackit.cloud) +- [STACKIT CLI](https://github.com/stackitcloud/stackit-cli) diff --git a/examples/iaas-image-upload/examples/terraform.tfvars.example b/examples/iaas-image-upload/examples/terraform.tfvars.example new file mode 100644 index 0000000..d6c7187 --- /dev/null +++ b/examples/iaas-image-upload/examples/terraform.tfvars.example @@ -0,0 +1,48 @@ +# Copy this file to terraform.tfvars and fill in the required values. +# terraform.tfvars is gitignored — never commit real project IDs or credentials. + +# ─── Provider ───────────────────────────────────────────────────────────────── + +stackit_region = "eu01" +stackit_service_account_key_path = "keys/sa-key.json" + +# ─── Project ────────────────────────────────────────────────────────────────── + +# Find your project ID in the STACKIT Portal or via: stackit project list +project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + +# ─── Image ──────────────────────────────────────────────────────────────────── + +image_name = "custom-image-v1" + +# Path to the local image file — this file must exist before running terraform apply. +# The images/ directory is gitignored; place your image file there. +image_file_path = "./images/custom-image.qcow2" + +disk_format = "qcow2" # supported: qcow2, raw, iso +min_disk_size = 20 +min_ram = 2048 + +# ─── Image Config ───────────────────────────────────────────────────────────── + +uefi = true +secure_boot = false + +# ─── Server ─────────────────────────────────────────────────────────────────── + +server_name = "custom-image-server" +machine_type = "g1.1" +availability_zone = "eu01-1" +boot_volume_size_gb = 20 +network_cidr = "10.10.0.0/24" +keypair_name = "custom-image-key" + +# Paste your SSH public key here (never the private key) +ssh_public_key = "ssh-ed25519 AAAA... your-key-comment" + +# ─── Labels ─────────────────────────────────────────────────────────────────── + +labels = { + managed-by = "terraform" + example = "image-upload" +} diff --git a/examples/iaas-image-upload/images/.gitkeep b/examples/iaas-image-upload/images/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/examples/iaas-volume-encryption/cloud-init.yaml b/examples/iaas-volume-encryption/cloud-init.yaml index 8b26cf3..653b5c6 100644 --- a/examples/iaas-volume-encryption/cloud-init.yaml +++ b/examples/iaas-volume-encryption/cloud-init.yaml @@ -1,17 +1,3 @@ -# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - #cloud-config users: - name: Administrator diff --git a/examples/opnsense-hub-and-spoke/001-hub-project/opnsense.qcow2 b/examples/opnsense-hub-and-spoke/001-hub-project/opnsense.qcow2 new file mode 100644 index 0000000..e166442 --- /dev/null +++ b/examples/opnsense-hub-and-spoke/001-hub-project/opnsense.qcow2 @@ -0,0 +1 @@ +.gitkeep diff --git a/examples/opnsense-hub-and-spoke/cloud-init/user-init-linux.yml b/examples/opnsense-hub-and-spoke/cloud-init/user-init-linux.yml index fb07b83..be6e579 100644 --- a/examples/opnsense-hub-and-spoke/cloud-init/user-init-linux.yml +++ b/examples/opnsense-hub-and-spoke/cloud-init/user-init-linux.yml @@ -1,17 +1,3 @@ -# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - #cloud-config # --------------------------------------------------------------------------- # Example cloud-init for Linux instances (RHEL / Debian). diff --git a/examples/opnsense-hub-and-spoke/cloud-init/user-init-windows.yml b/examples/opnsense-hub-and-spoke/cloud-init/user-init-windows.yml index e7bf80d..89103d4 100644 --- a/examples/opnsense-hub-and-spoke/cloud-init/user-init-windows.yml +++ b/examples/opnsense-hub-and-spoke/cloud-init/user-init-windows.yml @@ -1,17 +1,3 @@ -# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - #cloud-config # --------------------------------------------------------------------------- # Example cloud-init for Windows Server instances. diff --git a/examples/terraform-pg-backend-state-locking/00-bootstrap/.terraform.lock.hcl b/examples/terraform-pg-backend-state-locking/00-bootstrap/.terraform.lock.hcl new file mode 100644 index 0000000..ac34bd3 --- /dev/null +++ b/examples/terraform-pg-backend-state-locking/00-bootstrap/.terraform.lock.hcl @@ -0,0 +1,24 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/stackitcloud/stackit" { + version = "0.99.0" + constraints = ">= 0.99.0" + hashes = [ + "h1:a9z0j1z/8GmGjz+VygIhgyBbMqxx7jlXGqCvWBDD1NY=", + "zh:0dde99e7b343fa01f8eefc378171fb8621bedb20f59157d6cc8e3d46c738105f", + "zh:396c0392b9ef5ec7f8613c29a64e183545cc16dda0ceb876393fc003dba71c73", + "zh:40d86a1fb1c9ed4579583acb8ecc219edca44f9ee5221bfdcbc1bee2ce6654e7", + "zh:4ccbbecc3575737d87195ad13448d06071be9925760a2da5b7e5e8b91517f876", + "zh:506d786647c4566a82487fc3ffe0792f37a63ec8d6b54821aa3c7485e5ed6760", + "zh:848f638c500f1928f8593ae189472add1a0871c1e056d7df06871652ddee3409", + "zh:9ed739aec2c60cdfae3a33e4f349fa630fd0fd0ab50fcec5745774d42a6d6e70", + "zh:c0ac883dd73bd886e419d912c28ec29bb90a611b023cf4ae1b0534945cce1694", + "zh:df28663578694b25453b9d0a1cd7633a0f7fb1c113870cd3c133e9dc05d35946", + "zh:eaacb4a4512f41d44e46f82f042a19ab96c9d90d470890e2fd82c6cafb33bf0e", + "zh:ef9dd9b10571804f3a4dd6062405d0e473df270d75f05f897901c54d7d6c3d9d", + "zh:f40add9cd4fd4a7cda53f4a418c5f47a220b5ba5c4fc2377f60b1e16368f87d9", + "zh:f65deb30c1e3e8018a888d1aed56e895ea1e26b880f22a5772771e9836c9b5a4", + "zh:f8d14feddfd9d785d3ee6469937234a631998758ea5e8c16ecf61cdb94b07564", + ] +} diff --git a/examples/terraform-pg-backend-state-locking/00-bootstrap/000-variables.tf b/examples/terraform-pg-backend-state-locking/00-bootstrap/000-variables.tf new file mode 100644 index 0000000..95546e5 --- /dev/null +++ b/examples/terraform-pg-backend-state-locking/00-bootstrap/000-variables.tf @@ -0,0 +1,43 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +variable "stackit_service_account_key_path" { + description = "Path to the STACKIT service account key file (JSON). Keep this file out of version control." + type = string +} + +variable "stackit_organization_id" { + description = "STACKIT Organization ID (UUID). Found in the portal under Organization > Settings." + type = string +} + +variable "stackit_project_name" { + description = "Display name of the hub project in STACKIT." + type = string +} + +variable "stackit_org_admin" { + description = "Email address of the STACKIT user who will be set as project owner." + type = string +} + +variable "stackit_region" { + description = "STACKIT region (e.g. eu01)." + type = string +} + +variable "default_zone" { + description = "Availability zone within the region (e.g. eu01-1)." + type = string +} diff --git a/examples/terraform-pg-backend-state-locking/00-bootstrap/010-provider.tf b/examples/terraform-pg-backend-state-locking/00-bootstrap/010-provider.tf new file mode 100644 index 0000000..f57f226 --- /dev/null +++ b/examples/terraform-pg-backend-state-locking/00-bootstrap/010-provider.tf @@ -0,0 +1,27 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +terraform { + required_providers { + stackit = { + source = "stackitcloud/stackit" + version = ">= 0.99.0" + } + } +} + +provider "stackit" { + default_region = var.stackit_region + service_account_key_path = var.stackit_service_account_key_path +} diff --git a/examples/terraform-pg-backend-state-locking/00-bootstrap/020-project.tf b/examples/terraform-pg-backend-state-locking/00-bootstrap/020-project.tf new file mode 100644 index 0000000..1169d4f --- /dev/null +++ b/examples/terraform-pg-backend-state-locking/00-bootstrap/020-project.tf @@ -0,0 +1,19 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +resource "stackit_resourcemanager_project" "this" { + parent_container_id = var.stackit_organization_id + name = var.stackit_project_name + owner_email = var.stackit_org_admin +} diff --git a/examples/terraform-pg-backend-state-locking/00-bootstrap/030-postgresql_flex.tf b/examples/terraform-pg-backend-state-locking/00-bootstrap/030-postgresql_flex.tf new file mode 100644 index 0000000..db499da --- /dev/null +++ b/examples/terraform-pg-backend-state-locking/00-bootstrap/030-postgresql_flex.tf @@ -0,0 +1,54 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Provision PostgreSQL Flex Database Instance +resource "stackit_postgresflex_instance" "this" { + project_id = stackit_resourcemanager_project.this.project_id + name = "tf-state-instance" + version = "17" + + flavor = { + cpu = 2 + ram = 4 + } + storage = { + class = "premium-perf2-stackit" + size = 10 + } + + replicas = 1 + backup_schedule = "00 00 * * *" + + acl = [ + # WARNING: Open ACL is for development only. Restrict to your specific egress IP ranges in production. + "0.0.0.0/0" + ] + +} + +# Provision PostgreSQL Flex Database Owner +resource "stackit_postgresflex_user" "db_owner" { + project_id = stackit_resourcemanager_project.this.project_id + instance_id = stackit_postgresflex_instance.this.instance_id + username = "tf_state_user" + roles = ["login", "createdb"] +} + +# Provision PostgreSQL Flex Database +resource "stackit_postgresflex_database" "this" { + project_id = stackit_resourcemanager_project.this.project_id + instance_id = stackit_postgresflex_instance.this.instance_id + owner = stackit_postgresflex_user.db_owner.username + name = "tf-states" +} diff --git a/examples/terraform-pg-backend-state-locking/00-bootstrap/040-output.tf b/examples/terraform-pg-backend-state-locking/00-bootstrap/040-output.tf new file mode 100644 index 0000000..f345841 --- /dev/null +++ b/examples/terraform-pg-backend-state-locking/00-bootstrap/040-output.tf @@ -0,0 +1,27 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +locals { + pg_username = stackit_postgresflex_user.db_owner.username + pg_password = stackit_postgresflex_user.db_owner.password + pg_host = stackit_postgresflex_user.db_owner.host + pg_port = stackit_postgresflex_user.db_owner.port + pg_database = stackit_postgresflex_database.this.name +} + +output "pg_connection_uri" { + description = "PostgreSQL Flex User Connection String" + value = "postgres://${local.pg_username}:${local.pg_password}@${local.pg_host}:${local.pg_port}/${local.pg_database}?sslmode=require" + sensitive = true +} diff --git a/examples/terraform-pg-backend-state-locking/00-bootstrap/README.md b/examples/terraform-pg-backend-state-locking/00-bootstrap/README.md new file mode 100644 index 0000000..b8398d5 --- /dev/null +++ b/examples/terraform-pg-backend-state-locking/00-bootstrap/README.md @@ -0,0 +1,23 @@ +# Phase 0: Bootstrap + +This module provisions the STACKIT PostgreSQL Flex instance, the `terraform_state` database, and the dedicated `tf_state_user`. Its state is kept locally (or in an independent CI/CD backend) to prevent dependency conflicts. + +## Implementation Steps + +1. Initialize Terraform with the default local backend: + + ```sh + terraform init + ``` + +2. Provision the PostgreSQL Flex resources: + + ```sh + terraform apply + ``` + +3. Extract the generated PostgreSQL connection string from the Terraform outputs. This URI is required to configure the remote backend in the next phase. + + ```sh + terraform output -raw pg_connection_uri + ``` diff --git a/examples/terraform-pg-backend-state-locking/00-bootstrap/terraform.tfvars.example b/examples/terraform-pg-backend-state-locking/00-bootstrap/terraform.tfvars.example new file mode 100644 index 0000000..a144c2d --- /dev/null +++ b/examples/terraform-pg-backend-state-locking/00-bootstrap/terraform.tfvars.example @@ -0,0 +1,31 @@ +# --------------------------------------------------------------------------- +# terraform.tfvars.example +# +# Copy this file into a project directory (e.g. 00-bootcamp/terraform.tfvars) +# and fill in your values. Do NOT commit terraform.tfvars to version control. +# +# Alternatively, export variables as environment variables: +# export TF_VAR_stackit_organization_id="" +# --------------------------------------------------------------------------- + +# --- STACKIT Identity -------------------------------------------------------- + +# Your STACKIT Organization ID. +# Portal: Organization > Settings > Organization ID +stackit_organization_id = "" + +## Name of the bootstrapping project +stackit_project_name = "00-bootstrap" + +# Email address of the STACKIT user set as project owner. +stackit_org_admin = " Service Accounts > Keys > Create key +stackit_service_account_key_path = "./keys/service-account.json" + +# --- Region ------------------------------------------------------------------ +stackit_region = "eu01" +default_zone = "eu01-1" diff --git a/examples/terraform-pg-backend-state-locking/01-example-project/.terraform.lock.hcl b/examples/terraform-pg-backend-state-locking/01-example-project/.terraform.lock.hcl new file mode 100644 index 0000000..ac34bd3 --- /dev/null +++ b/examples/terraform-pg-backend-state-locking/01-example-project/.terraform.lock.hcl @@ -0,0 +1,24 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/stackitcloud/stackit" { + version = "0.99.0" + constraints = ">= 0.99.0" + hashes = [ + "h1:a9z0j1z/8GmGjz+VygIhgyBbMqxx7jlXGqCvWBDD1NY=", + "zh:0dde99e7b343fa01f8eefc378171fb8621bedb20f59157d6cc8e3d46c738105f", + "zh:396c0392b9ef5ec7f8613c29a64e183545cc16dda0ceb876393fc003dba71c73", + "zh:40d86a1fb1c9ed4579583acb8ecc219edca44f9ee5221bfdcbc1bee2ce6654e7", + "zh:4ccbbecc3575737d87195ad13448d06071be9925760a2da5b7e5e8b91517f876", + "zh:506d786647c4566a82487fc3ffe0792f37a63ec8d6b54821aa3c7485e5ed6760", + "zh:848f638c500f1928f8593ae189472add1a0871c1e056d7df06871652ddee3409", + "zh:9ed739aec2c60cdfae3a33e4f349fa630fd0fd0ab50fcec5745774d42a6d6e70", + "zh:c0ac883dd73bd886e419d912c28ec29bb90a611b023cf4ae1b0534945cce1694", + "zh:df28663578694b25453b9d0a1cd7633a0f7fb1c113870cd3c133e9dc05d35946", + "zh:eaacb4a4512f41d44e46f82f042a19ab96c9d90d470890e2fd82c6cafb33bf0e", + "zh:ef9dd9b10571804f3a4dd6062405d0e473df270d75f05f897901c54d7d6c3d9d", + "zh:f40add9cd4fd4a7cda53f4a418c5f47a220b5ba5c4fc2377f60b1e16368f87d9", + "zh:f65deb30c1e3e8018a888d1aed56e895ea1e26b880f22a5772771e9836c9b5a4", + "zh:f8d14feddfd9d785d3ee6469937234a631998758ea5e8c16ecf61cdb94b07564", + ] +} diff --git a/examples/terraform-pg-backend-state-locking/01-example-project/000-variables.tf b/examples/terraform-pg-backend-state-locking/01-example-project/000-variables.tf new file mode 100644 index 0000000..95546e5 --- /dev/null +++ b/examples/terraform-pg-backend-state-locking/01-example-project/000-variables.tf @@ -0,0 +1,43 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +variable "stackit_service_account_key_path" { + description = "Path to the STACKIT service account key file (JSON). Keep this file out of version control." + type = string +} + +variable "stackit_organization_id" { + description = "STACKIT Organization ID (UUID). Found in the portal under Organization > Settings." + type = string +} + +variable "stackit_project_name" { + description = "Display name of the hub project in STACKIT." + type = string +} + +variable "stackit_org_admin" { + description = "Email address of the STACKIT user who will be set as project owner." + type = string +} + +variable "stackit_region" { + description = "STACKIT region (e.g. eu01)." + type = string +} + +variable "default_zone" { + description = "Availability zone within the region (e.g. eu01-1)." + type = string +} diff --git a/examples/terraform-pg-backend-state-locking/01-example-project/010-backend.tf b/examples/terraform-pg-backend-state-locking/01-example-project/010-backend.tf new file mode 100644 index 0000000..3ea9fdf --- /dev/null +++ b/examples/terraform-pg-backend-state-locking/01-example-project/010-backend.tf @@ -0,0 +1,17 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +terraform { + backend "pg" {} +} diff --git a/examples/terraform-pg-backend-state-locking/01-example-project/020-provider.tf b/examples/terraform-pg-backend-state-locking/01-example-project/020-provider.tf new file mode 100644 index 0000000..f57f226 --- /dev/null +++ b/examples/terraform-pg-backend-state-locking/01-example-project/020-provider.tf @@ -0,0 +1,27 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +terraform { + required_providers { + stackit = { + source = "stackitcloud/stackit" + version = ">= 0.99.0" + } + } +} + +provider "stackit" { + default_region = var.stackit_region + service_account_key_path = var.stackit_service_account_key_path +} diff --git a/examples/terraform-pg-backend-state-locking/01-example-project/030-project.tf b/examples/terraform-pg-backend-state-locking/01-example-project/030-project.tf new file mode 100644 index 0000000..1169d4f --- /dev/null +++ b/examples/terraform-pg-backend-state-locking/01-example-project/030-project.tf @@ -0,0 +1,19 @@ +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +resource "stackit_resourcemanager_project" "this" { + parent_container_id = var.stackit_organization_id + name = var.stackit_project_name + owner_email = var.stackit_org_admin +} diff --git a/examples/terraform-pg-backend-state-locking/01-example-project/README.md b/examples/terraform-pg-backend-state-locking/01-example-project/README.md new file mode 100644 index 0000000..f1f211d --- /dev/null +++ b/examples/terraform-pg-backend-state-locking/01-example-project/README.md @@ -0,0 +1,56 @@ +# Phase 1: Example Infrastructure + +This module contains the core infrastructure configuration. It uses the `pg` backend to store state and enforce state locks via the STACKIT PostgreSQL Flex database provisioned in Phase 0. + +## Implementation Steps + +1. Create a `backend.conf` file in this directory and define the connection string using the credentials generated by the bootstrap module: + + ```ini + conn_str = "postgres://tf_state_user:@:5432/terraform_state?sslmode=require" + ``` + +2. Initialize Terraform and bind it to the remote PostgreSQL backend + + ```sh + terraform init -backend-config=backend.conf + ``` + +3. Provision the infrastructure or run the lock validation script: + + ```sh + chmod +x ./scripts/validate_lock.sh + ./scripts/validate_lock.sh + ``` + +## Log Output + +```sh +➜ 01-example-project ~ ./scripts/test-state-lock.sh +[INFO] Initiating background 'terraform apply' to acquire the state lock... +[INFO] Attempting concurrent 'terraform plan'... +[INFO] ------------------------------------------------------------------ +╷ +│ Error: Error acquiring the state lock +│ +│ Error message: Already locked for workspace creation: default +│ Lock Info: +│ ID: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXXXXXX +│ Path: +│ Operation: OperationTypePlan +│ Who: XXXX +│ Version: 1.14.9 +│ Created: 2026-06-25 16:21:59.636986 +0000 UTC +│ Info: +│ +│ +│ Terraform acquires a state lock to protect the state from being written +│ by multiple users at the same time. Please resolve the issue above and try +│ again. For most commands, you can disable locking with the "-lock=false" +│ flag, but this is not recommended. +╵ +[INFO] ------------------------------------------------------------------ +[SUCCESS] Concurrent operation rejected. State locking is active and functional. +[INFO] Waiting for the background 'terraform apply' process to terminate... +[INFO] Evaluation complete. Cleaning up temporary logs... +``` diff --git a/examples/terraform-pg-backend-state-locking/01-example-project/backend.conf.example b/examples/terraform-pg-backend-state-locking/01-example-project/backend.conf.example new file mode 100644 index 0000000..a44a0ab --- /dev/null +++ b/examples/terraform-pg-backend-state-locking/01-example-project/backend.conf.example @@ -0,0 +1 @@ +conn_str="" diff --git a/examples/terraform-pg-backend-state-locking/01-example-project/scripts/validate_lock.sh b/examples/terraform-pg-backend-state-locking/01-example-project/scripts/validate_lock.sh new file mode 100755 index 0000000..d5ec204 --- /dev/null +++ b/examples/terraform-pg-backend-state-locking/01-example-project/scripts/validate_lock.sh @@ -0,0 +1,44 @@ +#!/bin/bash +# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +set -u + +echo "[INFO] Initiating background 'terraform apply' to acquire the state lock..." +# Redirecting output to avoid console clutter during the concurrent test +terraform apply -auto-approve > apply_bg.log 2>&1 & +APPLY_PID=$! + +echo "[INFO] Attempting concurrent 'terraform plan'..." +echo "[INFO] ------------------------------------------------------------------" + +# Disable exit-on-error to capture the expected failure code +set +e +terraform plan +PLAN_EXIT_CODE=$? +set -e + +echo "[INFO] ------------------------------------------------------------------" + +if [ $PLAN_EXIT_CODE -ne 0 ]; then + echo "[SUCCESS] Concurrent operation rejected. State locking is active and functional." +else + echo "[ERROR] Concurrent operation succeeded. State locking failed or is misconfigured." +fi + +echo "[INFO] Waiting for the background 'terraform apply' process to terminate..." +wait $APPLY_PID + +echo "[INFO] Evaluation complete. Cleaning up temporary logs..." +rm -f apply_bg.log diff --git a/examples/terraform-pg-backend-state-locking/01-example-project/terraform.tfvars.example b/examples/terraform-pg-backend-state-locking/01-example-project/terraform.tfvars.example new file mode 100644 index 0000000..d16ff30 --- /dev/null +++ b/examples/terraform-pg-backend-state-locking/01-example-project/terraform.tfvars.example @@ -0,0 +1,31 @@ +# --------------------------------------------------------------------------- +# terraform.tfvars.example +# +# Copy this file into a project directory (e.g. 00-bootcamp/terraform.tfvars) +# and fill in your values. Do NOT commit terraform.tfvars to version control. +# +# Alternatively, export variables as environment variables: +# export TF_VAR_stackit_organization_id="" +# --------------------------------------------------------------------------- + +# --- STACKIT Identity -------------------------------------------------------- + +# Your STACKIT Organization ID. +# Portal: Organization > Settings > Organization ID +stackit_organization_id = "" + +## Name of the bootstrapping project +stackit_project_name = "01-example-project" + +# Email address of the STACKIT user set as project owner. +stackit_org_admin = " Service Accounts > Keys > Create key +stackit_service_account_key_path = "./keys/service-account.json" + +# --- Region ------------------------------------------------------------------ +stackit_region = "eu01" +default_zone = "eu01-1" diff --git a/examples/terraform-pg-backend-state-locking/MAINTAINERS.md b/examples/terraform-pg-backend-state-locking/MAINTAINERS.md new file mode 100644 index 0000000..f852b06 --- /dev/null +++ b/examples/terraform-pg-backend-state-locking/MAINTAINERS.md @@ -0,0 +1,9 @@ +# Maintainers + +General maintainers: + +- Tim Reibe () + +This example is actively maintained. The owner is responsible for reviewing and updating dependencies and functionalities on a monthly basis. +For questions, issues, or feature requests, please email general maintainers. +Please include the BP name and version in your request. We will track your request as an issue. diff --git a/examples/terraform-pg-backend-state-locking/README.md b/examples/terraform-pg-backend-state-locking/README.md new file mode 100644 index 0000000..721bd47 --- /dev/null +++ b/examples/terraform-pg-backend-state-locking/README.md @@ -0,0 +1,12 @@ +# STACKIT Terraform PostgreSQL Backend with State Locking + +This repository demonstrates how to configure STACKIT PostgreSQL Flex as a Terraform backend to enable remote state storage and native state locking. + +To resolve the circular dependency of provisioning a state backend using Terraform, the deployment is split into two isolated stages: + +1. **`00-bootstrap/`**: Provisions the backend infrastructure (PostgreSQL Flex instance, database and service user). +2. **`01-example/`**: Represents the primary infrastructure, utilizing the provisioned PostgreSQL database as its remote backend. + +--- + +**⚠️ Security Notice:** The PostgreSQL Flex instance in `00-bootstrap/` is configured with an open ACL (`0.0.0.0/0`) for development convenience. Before deploying to production, restrict the ACL to your specific egress IP ranges to prevent the database from being accessible via the public internet. diff --git a/examples/vpn-usecases/modules/stackit-sna-with-debug-machine/debug-user.yml b/examples/vpn-usecases/modules/stackit-sna-with-debug-machine/debug-user.yml index 90b0227..865ffb5 100644 --- a/examples/vpn-usecases/modules/stackit-sna-with-debug-machine/debug-user.yml +++ b/examples/vpn-usecases/modules/stackit-sna-with-debug-machine/debug-user.yml @@ -1,17 +1,3 @@ -# Copyright 2026 Schwarz Digits Cloud GmbH & Co. KG -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - #cloud-config # --------------------------------------------------------------------------- # Example cloud-init for Linux instances (RHEL / Debian).