Skip to content

Commit 2dcbef2

Browse files
committed
feat(terraform): replay STACKIT terraform infra changes
1 parent 3f9b6ea commit 2dcbef2

15 files changed

Lines changed: 385 additions & 11 deletions

File tree

infrastructure/.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
**/cr-secret.yaml
22
**/customer-values.yaml
33
**/auth
4-
4+
**/.backend.hcl
5+
**/kubeconfig.yaml
56
**/*.lock.*
67

78
auth

infrastructure/terraform/README.md

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,69 @@ terraform output -json | jq -r .cluster_name.value
9292
- **Security**: The sa_key.json file contains sensitive credentials and should never be committed to version control
9393
- **State Management**: Consider using remote state storage for team environments
9494

95+
## Using the bucket as a Terraform S3 backend (optional)
96+
97+
If you want Terraform state to be stored in the object storage bucket, add a backend block to your root module.
98+
Note: backend blocks cannot reference resources, so you must hardcode or pass the values via variables/partials.
99+
100+
### Bootstrap script (recommended)
101+
102+
Note: `backend "s3" {}` is already defined in `main.tf`. The bootstrap step still works because it runs `terraform init -backend=false`, which ignores the backend block.
103+
104+
Use the helper script to bootstrap the backend in two phases:
105+
1) Run a local-only apply to create the bucket + credentials.
106+
2) Generate `.backend.hcl` and migrate state to S3.
107+
108+
```bash
109+
./scripts/init-backend.sh
110+
```
111+
112+
This writes `infrastructure/terraform/.backend.hcl` (contains credentials) and runs `terraform init -force-copy`.
113+
You can re-run the script at any time; it reuses the existing backend config if present.
114+
If you want remote state from the start, run this script before your first full `terraform apply`.
115+
116+
If you want non-interactive bootstrap:
117+
118+
```bash
119+
BOOTSTRAP_AUTO_APPROVE=1 ./scripts/init-backend.sh
120+
```
121+
122+
Manual phase 1 (if you want to see the exact commands the script runs):
123+
124+
```bash
125+
terraform init -backend=false
126+
terraform apply \
127+
-target=stackit_objectstorage_bucket.tfstate \
128+
-target=stackit_objectstorage_credentials_group.rag_creds_group \
129+
-target=stackit_objectstorage_credential.rag_creds
130+
```
131+
132+
### Manual backend block
133+
134+
```hcl
135+
terraform {
136+
backend "s3" {
137+
bucket = "<BUCKET_NAME>"
138+
key = "terraform.tfstate"
139+
region = "eu01"
140+
141+
# Use the same credentials as above
142+
access_key = "<ACCESS_KEY>"
143+
secret_key = "<SECRET_KEY>"
144+
145+
endpoints = {
146+
s3 = "https://object.storage.eu01.onstackit.cloud"
147+
}
148+
149+
# AWS-specific checks must be disabled for STACKIT
150+
skip_credentials_validation = true
151+
skip_region_validation = true
152+
skip_s3_checksum = true
153+
skip_requesting_account_id = true
154+
}
155+
}
156+
```
157+
95158
## Cleanup
96159

97160
To destroy all resources:

infrastructure/terraform/dns.tf

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
resource "stackit_dns_zone" "rag_zone" {
2-
project_id = var.project_id
3-
name = "${var.name_prefix}-zone"
4-
dns_name = var.dns_name
2+
project_id = var.project_id
3+
name = "${var.name_prefix}-zone"
4+
dns_name = var.dns_name
5+
contact_email = "data-ai@stackit.cloud"
6+
type = "primary"
57
}
68

79
output "dns_nameservers" {

infrastructure/terraform/main.tf

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
terraform {
2+
backend "s3" {}
23
required_providers {
34
stackit = {
45
source = "stackitcloud/stackit"
@@ -9,4 +10,5 @@ terraform {
910

1011
provider "stackit" {
1112
service_account_key_path = "sa_key.json"
13+
default_region = "eu01"
1214
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
resource "stackit_modelserving_token" "rag_modelserving" {
2+
project_id = var.project_id
3+
name = "${var.name_prefix}-modelserving-token"
4+
5+
# No ttl_duration set -> token does not expire.
6+
}
7+
8+
output "model_serving_bearer_token" {
9+
description = "Bearer token for AI Model Serving API"
10+
value = stackit_modelserving_token.rag_modelserving.token
11+
sensitive = true
12+
}

infrastructure/terraform/object_storage.tf

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,19 @@
1+
# This resource stays stable for 365 days, then changes
2+
resource "time_rotating" "key_rotation" {
3+
rotation_days = 365
4+
}
5+
16
resource "stackit_objectstorage_bucket" "documents" {
27
name = "${var.name_prefix}-documents-${var.deployment_timestamp}"
38
project_id = var.project_id
49
}
510

11+
resource "stackit_objectstorage_bucket" "tfstate" {
12+
name = "${var.name_prefix}-tfstate-${var.deployment_timestamp}"
13+
project_id = var.project_id
14+
depends_on = [stackit_objectstorage_credentials_group.rag_creds_group]
15+
}
16+
617
resource "stackit_objectstorage_bucket" "langfuse" {
718
name = "${var.name_prefix}-langfuse-${var.deployment_timestamp}"
819
project_id = var.project_id
@@ -16,7 +27,7 @@ resource "stackit_objectstorage_credentials_group" "rag_creds_group" {
1627
resource "stackit_objectstorage_credential" "rag_creds" {
1728
project_id = var.project_id
1829
credentials_group_id = stackit_objectstorage_credentials_group.rag_creds_group.credentials_group_id
19-
expiration_timestamp = timeadd(timestamp(), "8760h") # Expires after 1 year
30+
expiration_timestamp = timeadd(time_rotating.key_rotation.rfc3339, "8760h")
2031
}
2132

2233
output "object_storage_access_key" {
@@ -30,5 +41,5 @@ output "object_storage_secret_key" {
3041
}
3142

3243
output "object_storage_bucket" {
33-
value = stackit_objectstorage_bucket.documents.name
44+
value = stackit_objectstorage_bucket.tfstate.name
3445
}

infrastructure/terraform/redis.tf

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
resource "stackit_redis_instance" "rag_redis" {
2+
project_id = var.project_id
3+
name = "${var.name_prefix}-redis"
4+
version = var.redis_version
5+
plan_name = var.redis_plan_name
6+
7+
parameters = {
8+
sgw_acl = join(",", stackit_ske_cluster.rag_cluster.egress_address_ranges)
9+
enable_monitoring = false
10+
down_after_milliseconds = 30000
11+
}
12+
}
13+
14+
15+
resource "stackit_redis_credential" "rag_redis_cred" {
16+
project_id = var.project_id
17+
instance_id = stackit_redis_instance.rag_redis.instance_id
18+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
5+
root_dir="$(cd "${script_dir}/.." && pwd)"
6+
7+
backend_config_file="${BACKEND_CONFIG_FILE:-${root_dir}/.backend.hcl}"
8+
auto_approve="${BOOTSTRAP_AUTO_APPROVE:-0}"
9+
10+
cd "${root_dir}"
11+
12+
if ! command -v terraform >/dev/null 2>&1; then
13+
echo "terraform is not installed or not in PATH." >&2
14+
exit 1
15+
fi
16+
17+
if [ -f "${backend_config_file}" ]; then
18+
terraform init -backend-config="${backend_config_file}"
19+
exit 0
20+
fi
21+
22+
echo "Bootstrapping object storage for Terraform state (local backend)."
23+
terraform init -backend=false
24+
25+
if ! bucket="$(terraform output -raw object_storage_bucket 2>/dev/null)"; then
26+
apply_args=(
27+
"-target=stackit_objectstorage_bucket.tfstate"
28+
"-target=stackit_objectstorage_credentials_group.rag_creds_group"
29+
"-target=stackit_objectstorage_credential.rag_creds"
30+
"-target=time_rotating.key_rotation" # <--- Add this (needed for creds)
31+
"-target=output.object_storage_bucket" # <--- Add this
32+
"-target=output.object_storage_access_key" # <--- Add this
33+
"-target=output.object_storage_secret_key" # <--- Add this
34+
)
35+
if [ "${auto_approve}" = "1" ]; then
36+
terraform apply -auto-approve "${apply_args[@]}"
37+
else
38+
terraform apply "${apply_args[@]}"
39+
fi
40+
bucket="$(terraform output -raw object_storage_bucket)"
41+
fi
42+
43+
access_key="$(terraform output -raw object_storage_access_key)"
44+
secret_key="$(terraform output -raw object_storage_secret_key)"
45+
46+
cat > "${backend_config_file}" <<EOF
47+
bucket = "${bucket}"
48+
key = "terraform.tfstate"
49+
region = "eu01"
50+
51+
access_key = "${access_key}"
52+
secret_key = "${secret_key}"
53+
54+
endpoints = {
55+
s3 = "https://object.storage.eu01.onstackit.cloud"
56+
}
57+
58+
skip_credentials_validation = true
59+
skip_region_validation = true
60+
skip_s3_checksum = true
61+
skip_requesting_account_id = true
62+
EOF
63+
64+
chmod 600 "${backend_config_file}"
65+
66+
terraform init -backend-config="${backend_config_file}" -force-copy
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
resource "stackit_secretsmanager_instance" "rag_secrets" {
2+
project_id = var.project_id
3+
name = "${var.name_prefix}-secrets"
4+
}
5+
6+
resource "stackit_secretsmanager_user" "rag_secrets_user" {
7+
project_id = var.project_id
8+
instance_id = stackit_secretsmanager_instance.rag_secrets.instance_id
9+
description = var.secretsmanager_user_description
10+
write_enabled = var.secretsmanager_user_write_enabled
11+
}
12+
13+
output "secretsmanager_instance_id" {
14+
value = stackit_secretsmanager_instance.rag_secrets.instance_id
15+
}
16+
17+
output "secretsmanager_username" {
18+
value = stackit_secretsmanager_user.rag_secrets_user.username
19+
}
20+
21+
output "secretsmanager_password" {
22+
value = stackit_secretsmanager_user.rag_secrets_user.password
23+
sensitive = true
24+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Seed Secrets Manager data with Terraform
2+
3+
This folder writes the `rag-secrets` KV secret used by External Secrets.
4+
5+
Why this is a separate step:
6+
- The Secrets Manager instance ID and user credentials are created by the main Terraform stack.
7+
- Provider blocks cannot depend on resources, so we pass them in via variables and run a second apply.
8+
9+
## Steps
10+
11+
1. Apply the main stack to create the Secrets Manager instance and user.
12+
2. Copy `terraform.tfvars.example` to `terraform.tfvars` and fill in the values:
13+
- `vault_mount_path` is the Secrets Manager instance ID.
14+
- `vault_username`/`vault_password` are from the `secretsmanager_*` outputs.
15+
- `rag_secrets` should include all keys referenced by your ExternalSecret resources.
16+
For the cert-manager webhook, store the service account key JSON under `STACKIT_CERT_MANAGER_SA_JSON` (use a heredoc to avoid escaping).
17+
3. Run:
18+
```bash
19+
terraform init
20+
terraform plan
21+
terraform apply
22+
```
23+
24+
## Security note
25+
26+
All values written by `vault_kv_secret_v2` are stored in Terraform state. Use a secure backend and restrict access.
27+
28+
## Troubleshooting
29+
30+
If you see a 404 from `auth/token/create`, the backend does not allow child token creation.
31+
This module sets `skip_child_token = true` so Vault uses the login token directly.

0 commit comments

Comments
 (0)