diff --git a/.agents/references/terraform-conventions.md b/.agents/references/terraform-conventions.md
index c9fbbb215..a364bd9d0 100644
--- a/.agents/references/terraform-conventions.md
+++ b/.agents/references/terraform-conventions.md
@@ -94,6 +94,12 @@ The comment sits directly above the `try`, or above the attribute that owns the
`try` is nested inside it. Prefer not needing it: a variable default, `optional()` or an explicit
`null` check says the same thing without hiding the next error too.
+
+## Summary Output
+
+Every building block with an integration has a `summary` output, declared in the BBD with
+`assignment_type = "SUMMARY"`, so meshPanel shows the result of a run.
+
## Output Content: Inline Heredoc Over a Template File
Write a markdown output's content — the `summary` output most of all — **inline with a `<<-EOT`
diff --git a/.agents/skills/module/SKILL.md b/.agents/skills/module/SKILL.md
index f3692a7fe..bcc7c99e2 100644
--- a/.agents/skills/module/SKILL.md
+++ b/.agents/skills/module/SKILL.md
@@ -29,6 +29,7 @@ conventions, see the reference files in `.agents/references/`.
```
3. **Implement `buildingblock/`** — `main.tf`, `variables.tf`, `outputs.tf`, `versions.tf`, `provider.tf`, `README.md` (with YAML front-matter), `logo.png` or `logo.svg`
+ - `outputs.tf` has a `summary` output (see [Summary Output](../../references/terraform-conventions.md#summary-output))
4. **Implement `backplane/`** (if needed) — read the provider-specific reference:
- AWS → `.agents/references/aws-backplane.md`
diff --git a/modules/kubernetes/ingress/buildingblock/README.md b/modules/kubernetes/ingress/buildingblock/README.md
index 584a0f030..933cf9c4f 100644
--- a/modules/kubernetes/ingress/buildingblock/README.md
+++ b/modules/kubernetes/ingress/buildingblock/README.md
@@ -141,6 +141,7 @@ puts Helm's ownership label and annotations on the ClusterIssuer, so the release
| [haproxy\_lb\_ip](#output\_haproxy\_lb\_ip) | External IP of the HAProxy LoadBalancer service. Point application DNS A records here before TLS provisioning can complete. |
| [haproxy\_namespace](#output\_haproxy\_namespace) | Namespace of the HAProxy ingress controller and of the wildcard certificate secret. |
| [ingress\_class\_name](#output\_ingress\_class\_name) | Name of the IngressClass an application puts on its Ingress to be served by this controller. |
+| [summary](#output\_summary) | Markdown summary shown on the building block. |
| [wildcard\_certificate\_domain](#output\_wildcard\_certificate\_domain) | Domain the wildcard certificate `*.` covers; null without `dns01`. |
| [wildcard\_certificate\_secret\_name](#output\_wildcard\_certificate\_secret\_name) | Secret in `haproxy_namespace` holding the wildcard certificate; null without `dns01`. |
diff --git a/modules/kubernetes/ingress/buildingblock/outputs.tf b/modules/kubernetes/ingress/buildingblock/outputs.tf
index fe859943c..1dffd2f6f 100644
--- a/modules/kubernetes/ingress/buildingblock/outputs.tf
+++ b/modules/kubernetes/ingress/buildingblock/outputs.tf
@@ -35,3 +35,49 @@ output "wildcard_certificate_secret_name" {
depends_on = [module.issuer]
}
+
+output "summary" {
+ description = "Markdown summary shown on the building block."
+ value = <<-EOT
+ # Kubernetes Ingress
+
+ | Property | Value |
+ |----------|-------|
+ | **Load Balancer IP** | `${data.kubernetes_service_v1.haproxy_controller.status[0].load_balancer[0].ingress[0].ip}` |
+ | **IngressClass** | `${var.ingress_class_name}` |
+ | **ClusterIssuer** | `${var.cluster_issuer_name}`, registered at `${var.acme_server}` |
+ | **Certificates** | ${local.dns01_enabled ? "wildcard `*.${local.dns01_certificate_domain}` in `${module.haproxy_namespace.name}/${var.wildcard_certificate_name}`, served by default" : "one per hostname, over HTTP-01"} |
+ | **HAProxy** | chart `${var.haproxy_version}`, ${var.haproxy_replica_count} replicas in `${module.haproxy_namespace.name}` |
+ | **cert-manager** | chart `${var.cert_manager_version}` in `${module.cert_manager_namespace.name}` |
+
+ Point the DNS records of your hostnames at the load balancer IP. An Ingress served by this
+ controller, with a certificate from Let's Encrypt:
+
+ ```yaml
+ apiVersion: networking.k8s.io/v1
+ kind: Ingress
+ metadata:
+ name: my-app
+ annotations:
+ cert-manager.io/cluster-issuer: ${var.cluster_issuer_name}
+ spec:
+ ingressClassName: ${var.ingress_class_name}
+ tls:
+ - hosts: [my-app.example.com]
+ secretName: my-app-tls
+ rules:
+ - host: my-app.example.com
+ http:
+ paths:
+ - path: /
+ pathType: Prefix
+ backend:
+ service:
+ name: my-app
+ port:
+ number: 80
+ ```
+ EOT
+
+ depends_on = [module.haproxy, module.issuer]
+}
diff --git a/modules/kubernetes/ingress/meshstack_integration.tf b/modules/kubernetes/ingress/meshstack_integration.tf
index a2ace23e9..09ace6532 100644
--- a/modules/kubernetes/ingress/meshstack_integration.tf
+++ b/modules/kubernetes/ingress/meshstack_integration.tf
@@ -60,7 +60,7 @@ variable "cluster_definition_ref" {
uuid = string
})
default = null
- description = "Ref of the SKE cluster definition whose building blocks parent this definition's building blocks; null declares no dependency."
+ description = "Ref of the cluster definition (for example `ske/cluster`) whose building blocks parent this definition's building blocks; null declares no dependency."
}
variable "bbd_display_name" {
@@ -309,6 +309,12 @@ resource "meshstack_building_block_definition" "this" {
type = "STRING"
assignment_type = "NONE"
}
+
+ summary = {
+ display_name = "Summary"
+ type = "STRING"
+ assignment_type = "SUMMARY"
+ }
}
permissions = []
diff --git a/modules/kubernetes/manifest/meshstack_integration.tf b/modules/kubernetes/manifest/meshstack_integration.tf
index 39049d295..6e72b4d6e 100644
--- a/modules/kubernetes/manifest/meshstack_integration.tf
+++ b/modules/kubernetes/manifest/meshstack_integration.tf
@@ -118,7 +118,7 @@ resource "meshstack_building_block_definition" "this" {
symbol = "https://raw.githubusercontent.com/meshcloud/meshstack-hub/${var.hub.git_ref}/modules/kubernetes/manifest/buildingblock/logo.svg"
description = coalesce(var.bbd_description, "Deploys arbitrary Kubernetes manifests into a tenant namespace via a local Helm chart, with operator-supplied templates and user-provided values.")
target_type = "TENANT_LEVEL"
- # TODO Correct would be to depend on the specific SKE cluster deployed (not just platform type),
+ # TODO Correct would be to depend on the specific cluster deployed (not just platform type),
# as the kubeconfig.yaml input only works for one specific cluster (representing the meshPlatform here)
supported_platforms = [{ name = "KUBERNETES" }]
run_transparency = true
diff --git a/modules/kubernetes/meshstack_integration.tf b/modules/kubernetes/meshstack_integration.tf
index 2db6112ef..fc0c3e95f 100644
--- a/modules/kubernetes/meshstack_integration.tf
+++ b/modules/kubernetes/meshstack_integration.tf
@@ -195,7 +195,7 @@ variable "cluster_definition_ref" {
uuid = string
})
default = null
- description = "Ref of the SKE cluster definition whose building blocks parent this definition's building blocks; null declares no dependency."
+ description = "Ref of the cluster definition (for example `ske/cluster`) whose building blocks parent this definition's building blocks; null declares no dependency."
}
variable "bbd_display_name" {
@@ -425,9 +425,9 @@ resource "meshstack_building_block_definition" "this" {
## 🎯 When to use it
A platform team orders this once per cluster, as part of turning that cluster into a meshStack
- platform — the **STACKIT Kubernetes Platform** reference architecture does it right after the
- cluster exists and feeds the tokens into the platform it registers. It reaches the cluster
- through the kubeconfig the ordering composition passes as an input.
+ platform. The **STACKIT Kubernetes Platform** reference architecture, for example, does it right
+ after the cluster exists and feeds the tokens into the platform it registers. It reaches the
+ cluster through the kubeconfig the ordering composition passes as an input.
## 📦 Resources created
diff --git a/modules/kubernetes/platform/README.md b/modules/kubernetes/platform/README.md
index ba2056b0f..f4f7ff3c1 100644
--- a/modules/kubernetes/platform/README.md
+++ b/modules/kubernetes/platform/README.md
@@ -28,9 +28,9 @@ is involved, so this runs on any conformant cluster.
It does **not** register a meshPlatform. Deciding that a cluster becomes a meshStack platform — its
identifier, location, landing zones and quota definitions — belongs to the composition that owns
-the cluster, and the **STACKIT Kubernetes Platform** reference architecture does it in its own
-`platform.tf` using the two tokens this block outputs. With `output_to_vault` set, it writes them to
-the Vault KV v2 secret `kubernetes/platform` under the keys `replicator-token` and
+the cluster. The **STACKIT Kubernetes Platform** reference architecture, for example, does it in
+its own `platform.tf` using the two tokens this block outputs. With `output_to_vault` set, it writes
+them to the Vault KV v2 secret `kubernetes/platform` under the keys `replicator-token` and
`metering-token` instead, and both outputs are empty.
## The kubeconfig is an input of the building block
diff --git a/modules/kubernetes/platform/main.tf b/modules/kubernetes/platform/main.tf
index 981a6c4dd..00e17927f 100644
--- a/modules/kubernetes/platform/main.tf
+++ b/modules/kubernetes/platform/main.tf
@@ -1,7 +1,8 @@
# The names below are cluster-global: `meshfed-service` and `meshfed-metering` are ClusterRoles, and
-# two of these deployed to one cluster would collide. That is fine here — each platform ordered from
-# the STACKIT Kubernetes Platform reference architecture gets a cluster of its own — and it is why
-# the upstream module's `name_suffix` / `existing_clusterrole_name` machinery is not carried over.
+# two of these deployed to one cluster would collide. That is fine while every cluster carries one
+# meshStack platform (the STACKIT Kubernetes Platform reference architecture gives each its own),
+# and it is why the upstream module's `name_suffix` / `existing_clusterrole_name` machinery is not
+# carried over.
# Reintroduce it if a cluster ever has to carry more than one meshStack platform registration.
locals {
replicator_name = "meshfed-service"
diff --git a/modules/kubernetes/platform/output_to_vault.tf b/modules/kubernetes/platform/output_to_vault.tf
index 806d1e60b..7308c021c 100644
--- a/modules/kubernetes/platform/output_to_vault.tf
+++ b/modules/kubernetes/platform/output_to_vault.tf
@@ -13,7 +13,8 @@ locals {
provider "vault" {
address = var.output_to_vault == null ? null : var.output_to_vault.address
- # A Secrets Manager user may not create the child token the provider asks for by default.
+ # A Vault user without `auth/token/create`, such as a STACKIT Secrets Manager user, cannot create
+ # the child token the provider asks for by default.
skip_child_token = true
dynamic "auth_login_userpass" {
diff --git a/modules/kubernetes/service-account/buildingblock/README.md b/modules/kubernetes/service-account/buildingblock/README.md
index 4ff56b8e3..cad83b6fd 100644
--- a/modules/kubernetes/service-account/buildingblock/README.md
+++ b/modules/kubernetes/service-account/buildingblock/README.md
@@ -71,6 +71,7 @@ No modules.
| Name | Description |
|------|-------------|
| [instructions](#output\_instructions) | Instructions for using the kubeconfig |
-| [kubeconfig](#output\_kubeconfig) | Kubeconfig file content for authenticating with the Kubernetes cluster. Empty when `output_to_vault` is set. |
+| [kubeconfig](#output\_kubeconfig) | n/a |
+| [summary](#output\_summary) | Markdown summary shown on the building block. |
| [vault\_secret](#output\_vault\_secret) | `{path, secret_hash}` of the secret in Vault, where `secret_hash` changes with its content; `{}` without `output_to_vault`. |
diff --git a/modules/kubernetes/service-account/buildingblock/output_to_vault.tf b/modules/kubernetes/service-account/buildingblock/output_to_vault.tf
index 2420ac671..fab61ee2b 100644
--- a/modules/kubernetes/service-account/buildingblock/output_to_vault.tf
+++ b/modules/kubernetes/service-account/buildingblock/output_to_vault.tf
@@ -1,6 +1,7 @@
locals {
write_to_vault = nonsensitive(var.output_to_vault != null)
vault_secret_path = local.write_to_vault ? "${nonsensitive(var.output_to_vault.path_prefix)}kubernetes/service-account" : null
+ vault_mount = local.write_to_vault ? nonsensitive(var.output_to_vault.mount) : null
vault_data_json = jsonencode({ kubeconfig = yamlencode(local.kubeconfig) })
@@ -10,7 +11,8 @@ locals {
provider "vault" {
address = var.output_to_vault == null ? null : var.output_to_vault.address
- # A Secrets Manager user may not create the child token the provider asks for by default.
+ # A Vault user without `auth/token/create`, such as a STACKIT Secrets Manager user, cannot create
+ # the child token the provider asks for by default.
skip_child_token = true
dynamic "auth_login_userpass" {
diff --git a/modules/kubernetes/service-account/buildingblock/outputs.tf b/modules/kubernetes/service-account/buildingblock/outputs.tf
index 795221a49..708bea3f9 100644
--- a/modules/kubernetes/service-account/buildingblock/outputs.tf
+++ b/modules/kubernetes/service-account/buildingblock/outputs.tf
@@ -38,9 +38,8 @@ output "instructions" {
}
output "kubeconfig" {
- description = "Kubeconfig file content for authenticating with the Kubernetes cluster. Empty when `output_to_vault` is set."
- sensitive = true
- value = local.write_to_vault ? "" : yamlencode(local.kubeconfig)
+ # "" because meshStack fails a run whose declared output is missing.
+ value = local.write_to_vault ? "" : nonsensitive(yamlencode(local.kubeconfig))
}
output "vault_secret" {
@@ -52,3 +51,19 @@ output "vault_secret" {
} : key => value if local.write_to_vault
}
}
+
+output "summary" {
+ description = "Markdown summary shown on the building block."
+ value = <<-EOT
+ # Kubernetes Service Account: **${kubernetes_service_account.this.metadata[0].name}**
+
+ | Property | Value |
+ |----------|-------|
+ | **Service Account** | `${var.namespace}/${kubernetes_service_account.this.metadata[0].name}` |
+ | **Role** | `${var.cluster_role}`, ${var.bind_cluster_wide ? "in every namespace" : "in `${var.namespace}`"} |
+ | **Cluster** | `${var.cluster_name}` at `${local.kubeconfig_cluster.server}` |
+ | **Kubeconfig** | ${local.write_to_vault ? "key `kubeconfig` of the Vault secret `${local.vault_secret_path}` on mount `${local.vault_mount}`" : "the **Kubeconfig** output of this building block"} |
+
+ Save the kubeconfig to a file and use it with `kubectl --kubeconfig kubeconfig get pods`.
+ EOT
+}
diff --git a/modules/kubernetes/service-account/meshstack_integration.tf b/modules/kubernetes/service-account/meshstack_integration.tf
index c3771a1a0..290cc03d9 100644
--- a/modules/kubernetes/service-account/meshstack_integration.tf
+++ b/modules/kubernetes/service-account/meshstack_integration.tf
@@ -268,6 +268,7 @@ resource "meshstack_building_block_definition" "this" {
assignment_type = "NONE"
}
+ # Carries the SA token in plain view, because BB outputs cannot be sensitive; output_to_vault leaves it "".
kubeconfig = {
display_name = "Kubeconfig"
type = "STRING"
@@ -279,6 +280,12 @@ resource "meshstack_building_block_definition" "this" {
type = "STRING"
assignment_type = "NONE"
}
+
+ summary = {
+ display_name = "Summary"
+ type = "STRING"
+ assignment_type = "SUMMARY"
+ }
}
permissions = []
diff --git a/modules/ske/cluster/buildingblock/README.md b/modules/ske/cluster/buildingblock/README.md
index 36cb5c147..024a53dd8 100644
--- a/modules/ske/cluster/buildingblock/README.md
+++ b/modules/ske/cluster/buildingblock/README.md
@@ -70,7 +70,8 @@ place. `release_on_destroy` cannot change once the cluster is managed.
| Name | Description |
|------|-------------|
| [cluster\_name](#output\_cluster\_name) | Name of the SKE cluster. |
-| [cluster\_url](#output\_cluster\_url) | Deep link to the STACKIT project the cluster lives in, in the STACKIT portal. |
+| [cluster\_url](#output\_cluster\_url) | Deep link to the cluster in the STACKIT portal. |
| [kube\_host](#output\_kube\_host) | Kubernetes API server URL of the cluster. |
+| [summary](#output\_summary) | Markdown summary shown on the building block. |
| [vault\_secret](#output\_vault\_secret) | `{path, secret_hash}` of the secret in Vault, where `secret_hash` changes with its content. |
diff --git a/modules/ske/cluster/buildingblock/output_to_vault.tf b/modules/ske/cluster/buildingblock/output_to_vault.tf
index 4371406f0..6559bb2eb 100644
--- a/modules/ske/cluster/buildingblock/output_to_vault.tf
+++ b/modules/ske/cluster/buildingblock/output_to_vault.tf
@@ -1,4 +1,6 @@
locals {
+ vault_address = nonsensitive(var.output_to_vault.address)
+ vault_mount = nonsensitive(var.output_to_vault.mount)
vault_secret_path = "${nonsensitive(var.output_to_vault.path_prefix)}ske/cluster"
vault_data_json = jsonencode({ kubeconfig = local.admin_kubeconfig })
diff --git a/modules/ske/cluster/buildingblock/outputs.tf b/modules/ske/cluster/buildingblock/outputs.tf
index e12371a9f..b3501d6f3 100644
--- a/modules/ske/cluster/buildingblock/outputs.tf
+++ b/modules/ske/cluster/buildingblock/outputs.tf
@@ -3,9 +3,13 @@ output "cluster_name" {
value = module.cluster.cluster.name
}
+locals {
+ cluster_url = "https://portal.stackit.cloud/kubernetes/clusters/details/${module.cluster.cluster.name}/overview?project=${var.stackit_project_id}"
+}
+
output "cluster_url" {
- description = "Deep link to the STACKIT project the cluster lives in, in the STACKIT portal."
- value = "https://portal.stackit.cloud/projects/${var.stackit_project_id}/ske/clusters"
+ description = "Deep link to the cluster in the STACKIT portal."
+ value = local.cluster_url
}
output "kube_host" {
@@ -20,3 +24,36 @@ output "vault_secret" {
secret_hash = local.vault_secret_hash
}
}
+
+output "summary" {
+ description = "Markdown summary shown on the building block."
+ value = <<-EOT
+ # SKE Cluster: **${module.cluster.cluster.name}**
+
+ | Property | Value |
+ |----------|-------|
+ | **Cluster** | [Open in STACKIT Portal](${local.cluster_url}) |
+ | **Kubernetes** | `${module.cluster.cluster.kubernetes_version_used}` |
+ | **API Server** | `${terraform_data.kube_host.output}` |
+ | **Node Pool** | `${var.node_pool.name}`: ${var.node_pool.minimum} to ${var.node_pool.maximum} × `${var.node_pool.machine_type}` in ${join(", ", var.node_pool.availability_zones)} |
+ | **Maintenance** | daily `${var.maintenance.start}` to `${var.maintenance.end}` |
+
+ ## Platform admin kubeconfig
+
+ The service account `kube-system/${local.admin_name}` is bound to `cluster-admin`. Its kubeconfig is
+ the key `kubeconfig` of the Vault secret `${local.vault_secret_path}` on mount `${local.vault_mount}`.
+ The token belongs to generation `${var.admin_token_generation}`; the one of the month before stays valid.
+
+ ```sh
+ export VAULT_ADDR=${local.vault_address}
+ export VAULT_TOKEN=$(stackit auth get-access-token)
+ vault kv get -mount=${local.vault_mount} -field=kubeconfig ${local.vault_secret_path} > kubeconfig
+ ```
+
+ For your own access, STACKIT issues a kubeconfig that expires:
+
+ ```sh
+ stackit ske kubeconfig create ${module.cluster.cluster.name} --project-id ${var.stackit_project_id}
+ ```
+ EOT
+}
diff --git a/modules/ske/cluster/meshstack_integration.tf b/modules/ske/cluster/meshstack_integration.tf
index 0c58dddfe..a378d44fc 100644
--- a/modules/ske/cluster/meshstack_integration.tf
+++ b/modules/ske/cluster/meshstack_integration.tf
@@ -289,6 +289,12 @@ resource "meshstack_building_block_definition" "this" {
type = "STRING"
assignment_type = "RESOURCE_URL"
}
+
+ summary = {
+ display_name = "Summary"
+ type = "STRING"
+ assignment_type = "SUMMARY"
+ }
}
permissions = []
diff --git a/modules/ske/forgejo-connector/buildingblock/README.md b/modules/ske/forgejo-connector/buildingblock/README.md
index e8ef3d3d7..e53646b0e 100644
--- a/modules/ske/forgejo-connector/buildingblock/README.md
+++ b/modules/ske/forgejo-connector/buildingblock/README.md
@@ -114,4 +114,5 @@ Secret of its own service account.
| Name | Description |
|------|-------------|
| [app\_link](#output\_app\_link) | Public URL for this stage application. |
+| [summary](#output\_summary) | Markdown summary shown on the building block. |
diff --git a/modules/ske/forgejo-connector/buildingblock/outputs.tf b/modules/ske/forgejo-connector/buildingblock/outputs.tf
index b39bdc0b2..50e53957e 100644
--- a/modules/ske/forgejo-connector/buildingblock/outputs.tf
+++ b/modules/ske/forgejo-connector/buildingblock/outputs.tf
@@ -1,4 +1,49 @@
+locals {
+ app_url = "https://${var.app_hostname}"
+ repository_url = "${trimsuffix(data.external.env.result["FORGEJO_HOST"], "/")}/${var.repository_owner}/${var.repository_name}"
+ actions_url = "${local.repository_url}/actions?workflow=pipeline.yaml"
+}
+
output "app_link" {
description = "Public URL for this stage application."
- value = "https://${var.app_hostname}"
+ value = local.app_url
+}
+
+output "actions_url" {
+ description = "Forgejo Actions page of the repository's `pipeline.yaml` workflow."
+ value = local.actions_url
+}
+
+output "summary" {
+ description = "Markdown summary shown on the building block."
+ value = <<-EOT
+ # Forgejo Connector: **${var.stage}**
+
+ The `${var.stage}` branch of [${var.repository_owner}/${var.repository_name}](${local.repository_url}) deploys
+ to the namespace `${var.namespace}`.
+
+ | Property | Value |
+ |----------|-------|
+ | **App** | [${local.app_url}](${local.app_url}) |
+ | **Pipeline** | [`pipeline.yaml` in Forgejo Actions](${local.actions_url}) |
+ | **Service Account** | `${kubernetes_service_account.forgejo_actions.metadata[0].name}`, `edit` on the namespace |
+
+ ## Forgejo Actions secrets and variables
+
+ Set in the [repository settings](${local.repository_url}/settings/actions/secrets):
+
+ | Name | Kind | Holds |
+ |------|------|-------|
+ | `KUBECONFIG_${upper(var.stage)}` | secret | kubeconfig of the service account |
+ | `K8S_NAMESPACE_${upper(var.stage)}` | variable | `${var.namespace}` |
+ | `APP_HOSTNAME_${upper(var.stage)}` | variable | `${var.app_hostname}` |
+
+ ## Kubernetes secrets in the namespace
+
+ | Secret | Filled from the Vault secret |
+ |--------|------------------------------|
+ | `${kubernetes_secret.image_pull.metadata[0].name}`, the image pull secret of `default` | `${var.registry_secret_path}` |${join("", [for name, path in var.additional_kubernetes_secrets : "\n| `${name}` | `${path}` |"])}
+
+ A changed Vault value reaches these secrets with the next increase of **Secrets Revision**.
+ EOT
}
diff --git a/modules/ske/forgejo-connector/meshstack_integration.tf b/modules/ske/forgejo-connector/meshstack_integration.tf
index 2026727cb..78c13d5e6 100644
--- a/modules/ske/forgejo-connector/meshstack_integration.tf
+++ b/modules/ske/forgejo-connector/meshstack_integration.tf
@@ -325,6 +325,18 @@ resource "meshstack_building_block_definition" "this" {
display_name = "Open App"
type = "STRING"
}
+
+ "actions_url" = {
+ assignment_type = "RESOURCE_URL"
+ display_name = "Open Forgejo Actions"
+ type = "STRING"
+ }
+
+ "summary" = {
+ assignment_type = "SUMMARY"
+ display_name = "Summary"
+ type = "STRING"
+ }
}
}
}
diff --git a/modules/stackit/ai-llm/buildingblock/README.md b/modules/stackit/ai-llm/buildingblock/README.md
index 86e8f2486..994ff080f 100644
--- a/modules/stackit/ai-llm/buildingblock/README.md
+++ b/modules/stackit/ai-llm/buildingblock/README.md
@@ -80,5 +80,6 @@ No modules.
| Name | Description |
|------|-------------|
| [summary](#output\_summary) | Markdown summary shown on the building block. |
+| [token\_url](#output\_token\_url) | STACKIT portal page of the model serving token. |
| [vault\_secret](#output\_vault\_secret) | `{path, secret_hash}` of the secret in Vault, where `secret_hash` changes with its content. |
diff --git a/modules/stackit/ai-llm/buildingblock/SUMMARY.md.tftpl b/modules/stackit/ai-llm/buildingblock/SUMMARY.md.tftpl
index 309158cda..b6bd0c539 100644
--- a/modules/stackit/ai-llm/buildingblock/SUMMARY.md.tftpl
+++ b/modules/stackit/ai-llm/buildingblock/SUMMARY.md.tftpl
@@ -4,7 +4,7 @@
|----------|-------|
| **Endpoint** | `${base_url}` |
| **Default model** | `${model}` |
-| **Token** | `${token_name}` |
+| **Token** | [`${token_name}`](${token_url}) |
| **Region** | `${region}` |
The endpoint speaks the OpenAI API, so any OpenAI client works against it by pointing its base URL
diff --git a/modules/stackit/ai-llm/buildingblock/outputs.tf b/modules/stackit/ai-llm/buildingblock/outputs.tf
index e70954eba..a09241a63 100644
--- a/modules/stackit/ai-llm/buildingblock/outputs.tf
+++ b/modules/stackit/ai-llm/buildingblock/outputs.tf
@@ -1,8 +1,18 @@
+locals {
+ token_url = "https://portal.stackit.cloud/model-serving/tokens/${stackit_modelserving_token.this.token_id}?project=${var.stackit_project_id}"
+}
+
+output "token_url" {
+ value = local.token_url
+ description = "STACKIT portal page of the model serving token."
+}
+
output "summary" {
value = templatefile("${path.module}/SUMMARY.md.tftpl", {
base_url = local.base_url
model = var.model
token_name = var.token_name
+ token_url = local.token_url
region = var.stackit_region
})
description = "Markdown summary shown on the building block."
diff --git a/modules/stackit/ai-llm/meshstack_integration.tf b/modules/stackit/ai-llm/meshstack_integration.tf
index e20231f4d..e6e30388c 100644
--- a/modules/stackit/ai-llm/meshstack_integration.tf
+++ b/modules/stackit/ai-llm/meshstack_integration.tf
@@ -229,6 +229,12 @@ resource "meshstack_building_block_definition" "this" {
assignment_type = "NONE"
}
+ token_url = {
+ display_name = "Open Portal"
+ type = "STRING"
+ assignment_type = "RESOURCE_URL"
+ }
+
summary = {
display_name = "Summary"
type = "STRING"
diff --git a/modules/stackit/container-registry/buildingblock/README.md b/modules/stackit/container-registry/buildingblock/README.md
index e1b0b8edd..b144bcdcd 100644
--- a/modules/stackit/container-registry/buildingblock/README.md
+++ b/modules/stackit/container-registry/buildingblock/README.md
@@ -177,6 +177,7 @@ it in the registry. The default is an empty list, which mirrors nothing.
| Name | Description |
|------|-------------|
+| [portal\_url](#output\_portal\_url) | Container registry page of the project in the STACKIT portal, which has no page for a single registry. |
| [registry\_host](#output\_registry\_host) | Host applications pull images from and `docker login` targets. |
| [registry\_name](#output\_registry\_name) | Name of the STACKIT container registry created in the project. |
| [registry\_robot\_url](#output\_registry\_robot\_url) | Robot Accounts tab of the Harbor project, where the robot linked to the service account is created. |
diff --git a/modules/stackit/container-registry/buildingblock/SUMMARY.md.tftpl b/modules/stackit/container-registry/buildingblock/SUMMARY.md.tftpl
index 1bdcba4cb..280d34092 100644
--- a/modules/stackit/container-registry/buildingblock/SUMMARY.md.tftpl
+++ b/modules/stackit/container-registry/buildingblock/SUMMARY.md.tftpl
@@ -4,7 +4,8 @@ A STACKIT container registry is ready in project `${stackit_project_id}`.
| Field | Value |
|-------|-------|
-| **Registry** | `${registry_url}` |
+| **Harbor** | [Repositories](${registry_url}) · [Robot accounts](${registry_robot_url}) |
+| **STACKIT Portal** | [Open in STACKIT Portal](${portal_url}) |
| **Host** | `${registry_host}` |
| **Pull path** | `${registry_host}/${registry_name}/:` |
diff --git a/modules/stackit/container-registry/buildingblock/main.tf b/modules/stackit/container-registry/buildingblock/main.tf
index f111cb95c..f70286dd6 100644
--- a/modules/stackit/container-registry/buildingblock/main.tf
+++ b/modules/stackit/container-registry/buildingblock/main.tf
@@ -31,6 +31,7 @@ locals {
# project, so read-artifactory.sh waits for it.
registry_url = data.external.artifactory.result.url
registry_robot_url = "${trimsuffix(local.registry_url, "/repositories")}/robot-account"
+ portal_url = "https://portal.stackit.cloud/container-registry-lite?project=${var.stackit_project_id}"
service_enablement_path = "/v2/projects/${var.stackit_project_id}/regions/${var.stackit_region}/services"
artifactory_path = "/v1/projects/${var.stackit_project_id}/regions/${var.stackit_region}/artifactories"
diff --git a/modules/stackit/container-registry/buildingblock/outputs.tf b/modules/stackit/container-registry/buildingblock/outputs.tf
index dd905898e..7dca793b7 100644
--- a/modules/stackit/container-registry/buildingblock/outputs.tf
+++ b/modules/stackit/container-registry/buildingblock/outputs.tf
@@ -18,6 +18,11 @@ output "registry_robot_url" {
value = local.registry_robot_url
}
+output "portal_url" {
+ description = "Container registry page of the project in the STACKIT portal, which has no page for a single registry."
+ value = local.portal_url
+}
+
output "summary" {
description = "Summary of the created registry and, until a robot is linked, the one manual step it still needs."
value = templatefile("${path.module}/SUMMARY.md.tftpl", {
@@ -25,6 +30,7 @@ output "summary" {
registry_host = local.registry_host
registry_url = local.registry_url
registry_robot_url = local.registry_robot_url
+ portal_url = local.portal_url
stackit_project_id = var.stackit_project_id
user_assignments = values(local.registry_role_assignments)
unknown_users = local.unknown_users
diff --git a/modules/stackit/container-registry/meshstack_integration.tf b/modules/stackit/container-registry/meshstack_integration.tf
index e03dd9921..93ac2f67f 100644
--- a/modules/stackit/container-registry/meshstack_integration.tf
+++ b/modules/stackit/container-registry/meshstack_integration.tf
@@ -299,6 +299,14 @@ resource "meshstack_building_block_definition" "this" {
display_name = "Open Registry"
type = "STRING"
assignment_type = "RESOURCE_URL"
+ display_order = 1
+ }
+
+ portal_url = {
+ display_name = "Open Portal"
+ type = "STRING"
+ assignment_type = "RESOURCE_URL"
+ display_order = 2
}
registry_robot_url = {
diff --git a/modules/stackit/dns/buildingblock/outputs.tf b/modules/stackit/dns/buildingblock/outputs.tf
index eddbb9975..df3c4f1dc 100644
--- a/modules/stackit/dns/buildingblock/outputs.tf
+++ b/modules/stackit/dns/buildingblock/outputs.tf
@@ -9,14 +9,14 @@ output "zone_id" {
}
output "zone_url" {
- value = "https://portal.stackit.cloud/projects/${var.stackit_project_id}/dns/${module.zone.zone.zone_id}"
+ value = "https://portal.stackit.cloud/dns/zones/${module.zone.zone.zone_id}/overview?project=${var.stackit_project_id}"
description = "The zone in the STACKIT portal."
}
output "summary" {
value = templatefile("${path.module}/SUMMARY.md.tftpl", {
zone_name = local.zone_name
- zone_url = "https://portal.stackit.cloud/projects/${var.stackit_project_id}/dns/${module.zone.zone.zone_id}"
+ zone_url = "https://portal.stackit.cloud/dns/zones/${module.zone.zone.zone_id}/overview?project=${var.stackit_project_id}"
wildcard_target_ip = var.wildcard_target_ip
default_ttl = var.default_ttl
})
diff --git a/modules/stackit/dns/meshstack_integration.tf b/modules/stackit/dns/meshstack_integration.tf
index 0818d0fd7..9c6baf526 100644
--- a/modules/stackit/dns/meshstack_integration.tf
+++ b/modules/stackit/dns/meshstack_integration.tf
@@ -259,7 +259,7 @@ resource "meshstack_building_block_definition" "this" {
}
zone_url = {
- display_name = "Open Zone"
+ display_name = "Open DNS Zone"
type = "STRING"
assignment_type = "RESOURCE_URL"
}
diff --git a/modules/stackit/git-runner/buildingblock/README.md b/modules/stackit/git-runner/buildingblock/README.md
index 6d3b07830..6bdb51219 100644
--- a/modules/stackit/git-runner/buildingblock/README.md
+++ b/modules/stackit/git-runner/buildingblock/README.md
@@ -70,5 +70,7 @@ No modules.
|------|-------------|
| [egress\_ip](#output\_egress\_ip) | Public SNAT IP of the runner's network router, for allowlisting. Empty on an existing network. |
| [runner\_name](#output\_runner\_name) | Name the runner registered under in STACKIT Git. |
+| [runners\_url](#output\_runners\_url) | Forgejo page of the organization's runners. |
| [server\_id](#output\_server\_id) | ID of the STACKIT server hosting the runner. |
+| [summary](#output\_summary) | Markdown summary shown on the building block. |
diff --git a/modules/stackit/git-runner/buildingblock/outputs.tf b/modules/stackit/git-runner/buildingblock/outputs.tf
index 99e5bc000..2dd487bc3 100644
--- a/modules/stackit/git-runner/buildingblock/outputs.tf
+++ b/modules/stackit/git-runner/buildingblock/outputs.tf
@@ -12,3 +12,30 @@ output "egress_ip" {
value = local.create_network ? stackit_network.this.public_ip : ""
description = "Public SNAT IP of the runner's network router, for allowlisting. Empty on an existing network."
}
+
+locals {
+ runners_url = "${var.git_base_url}/org/${var.git_organization}/settings/actions/runners"
+}
+
+output "runners_url" {
+ value = local.runners_url
+ description = "Forgejo page of the organization's runners."
+}
+
+output "summary" {
+ description = "Markdown summary shown on the building block."
+ value = <<-EOT
+ # Git Runner: **${var.name}**
+
+ The runner serves the STACKIT Git organization [`${var.git_organization}`](${var.git_base_url}/${var.git_organization}).
+ A workflow picks it with `runs-on` set to one of its labels.
+
+ | Property | Value |
+ |----------|-------|
+ | **Runners** | [Organization runners](${local.runners_url}) |
+ | **Labels** | `${join("`, `", var.runner_labels)}` |
+ | **Runner Version** | `${var.runner_version}` |
+ | **Server** | `${stackit_server.this.server_id}`: `${var.machine_type}` with ${var.disk_size_gb} GB in ${var.availability_zone} |
+ | **Egress IP** | ${local.create_network ? "`${stackit_network.this.public_ip}`" : "that of the network `${var.network_id}`"} |
+ EOT
+}
diff --git a/modules/stackit/git-runner/meshstack_integration.tf b/modules/stackit/git-runner/meshstack_integration.tf
index 9393db7a6..22fdc73da 100644
--- a/modules/stackit/git-runner/meshstack_integration.tf
+++ b/modules/stackit/git-runner/meshstack_integration.tf
@@ -317,6 +317,18 @@ resource "meshstack_building_block_definition" "this" {
type = "STRING"
assignment_type = "NONE"
}
+
+ runners_url = {
+ display_name = "Open Forgejo Runners"
+ type = "STRING"
+ assignment_type = "RESOURCE_URL"
+ }
+
+ summary = {
+ display_name = "Summary"
+ type = "STRING"
+ assignment_type = "SUMMARY"
+ }
}
}
}
diff --git a/modules/stackit/git/buildingblock/README.md b/modules/stackit/git/buildingblock/README.md
index ab812135f..2f560f1f4 100644
--- a/modules/stackit/git/buildingblock/README.md
+++ b/modules/stackit/git/buildingblock/README.md
@@ -165,5 +165,7 @@ With `release_on_destroy` set, destroying the building block leaves the instance
| [instance\_url](#output\_instance\_url) | URL of the Forgejo instance. |
| [local\_user\_username](#output\_local\_user\_username) | Username of the technical user the token belongs to. |
| [organization\_url](#output\_organization\_url) | URL of the Forgejo organization. Falls back to the instance URL when no organization was asked for. |
+| [portal\_url](#output\_portal\_url) | STACKIT portal page of the Git instance. |
+| [summary](#output\_summary) | Markdown summary shown on the building block. |
| [vault\_secret](#output\_vault\_secret) | `{path, secret_hash}` of the secret in Vault, where `secret_hash` changes with its content; `{}` without `output_to_vault`. |
diff --git a/modules/stackit/git/buildingblock/SUMMARY.md.tftpl b/modules/stackit/git/buildingblock/SUMMARY.md.tftpl
new file mode 100644
index 000000000..7e456a2b5
--- /dev/null
+++ b/modules/stackit/git/buildingblock/SUMMARY.md.tftpl
@@ -0,0 +1,29 @@
+# STACKIT Git: **${instance_name}**
+
+| Property | Value |
+|----------|-------|
+| **Forgejo** | [${instance_url}](${instance_url}) |
+| **STACKIT Portal** | [Open in STACKIT Portal](${portal_url}) |
+%{ if forgejo_organization != null ~}
+| **Organization** | [`${forgejo_organization}`](${organization_url}) |
+%{ endif ~}
+| **Technical User** | `${local_user_username}` |
+| **Shared Runner** | ${length(shared_runner_labels) > 0 ? "`${join("`, `", shared_runner_labels)}`" : "none"} |
+%{ if vault_secret_path != null ~}
+| **API Token** | key `forgejo-api-token` of the Vault secret `${vault_secret_path}` on mount `${vault_mount}` |
+%{ endif ~}
+%{ if length(members) > 0 ~}
+
+## Organization members
+
+| Member | Role | Forgejo user |
+|--------|------|--------------|
+%{ for member in members ~}
+| `${member.email}` | `${member.role}` | ${member.username != "" ? "✅ `${member.username}`" : "⚠️ not signed in yet"} |
+%{ endfor ~}
+%{ if length([for member in members : member if member.username == ""]) > 0 ~}
+
+A member joins their team once they have signed in to [${instance_url}](${instance_url}) with their
+STACKIT account. After they have signed in, re-run this building block.
+%{ endif ~}
+%{ endif ~}
diff --git a/modules/stackit/git/buildingblock/output_to_vault.tf b/modules/stackit/git/buildingblock/output_to_vault.tf
index 2db2dc3ed..1e3a37292 100644
--- a/modules/stackit/git/buildingblock/output_to_vault.tf
+++ b/modules/stackit/git/buildingblock/output_to_vault.tf
@@ -1,6 +1,7 @@
locals {
write_to_vault = nonsensitive(var.output_to_vault != null)
vault_secret_path = local.write_to_vault ? "${nonsensitive(var.output_to_vault.path_prefix)}stackit/git" : null
+ vault_mount = local.write_to_vault ? nonsensitive(var.output_to_vault.mount) : null
vault_data_json = jsonencode({ "forgejo-api-token" = local.forgejo_api_token })
diff --git a/modules/stackit/git/buildingblock/outputs.tf b/modules/stackit/git/buildingblock/outputs.tf
index 1394d3a81..74deccac8 100644
--- a/modules/stackit/git/buildingblock/outputs.tf
+++ b/modules/stackit/git/buildingblock/outputs.tf
@@ -13,11 +13,21 @@ output "instance_url" {
value = module.instance.instance.url
}
-output "organization_url" {
- description = "URL of the Forgejo organization. Falls back to the instance URL when no organization was asked for."
- value = local.create_forgejo_organization ? format(
+locals {
+ organization_url = local.create_forgejo_organization ? format(
"%s/%s", trimsuffix(module.instance.instance.url, "/"), var.forgejo_organization
) : module.instance.instance.url
+ portal_url = "https://portal.stackit.cloud/git/instances/${module.instance.instance.instance_id}/overview?project=${var.stackit_project_id}"
+}
+
+output "organization_url" {
+ description = "URL of the Forgejo organization. Falls back to the instance URL when no organization was asked for."
+ value = local.organization_url
+}
+
+output "portal_url" {
+ description = "STACKIT portal page of the Git instance."
+ value = local.portal_url
}
output "forgejo_organization" {
@@ -42,3 +52,21 @@ output "vault_secret" {
} : key => value if local.write_to_vault
}
}
+
+output "summary" {
+ description = "Markdown summary shown on the building block."
+ value = templatefile("${path.module}/SUMMARY.md.tftpl", {
+ instance_name = module.instance.instance.name
+ instance_url = module.instance.instance.url
+ portal_url = local.portal_url
+ forgejo_organization = local.create_forgejo_organization ? var.forgejo_organization : null
+ organization_url = local.organization_url
+ local_user_username = local.local_user_username
+ shared_runner_labels = var.shared_runner_labels
+ vault_secret_path = local.vault_secret_path
+ vault_mount = local.vault_mount
+ members = [
+ for email, role in local.member_roles : { email = email, role = role, username = lookup(local.forgejo_usernames, email, "") }
+ ]
+ })
+}
diff --git a/modules/stackit/git/meshstack_integration.tf b/modules/stackit/git/meshstack_integration.tf
index bfbb383d3..9777ab6ca 100644
--- a/modules/stackit/git/meshstack_integration.tf
+++ b/modules/stackit/git/meshstack_integration.tf
@@ -325,15 +325,24 @@ resource "meshstack_building_block_definition" "this" {
}
instance_url = {
- display_name = "Instance URL"
+ display_name = "Open Forgejo"
type = "STRING"
- assignment_type = "NONE"
+ assignment_type = "RESOURCE_URL"
+ display_order = 1
}
organization_url = {
display_name = "Open Forgejo Org"
type = "STRING"
assignment_type = "RESOURCE_URL"
+ display_order = 2
+ }
+
+ portal_url = {
+ display_name = "Open Portal"
+ type = "STRING"
+ assignment_type = "RESOURCE_URL"
+ display_order = 3
}
forgejo_organization = {
@@ -347,6 +356,12 @@ resource "meshstack_building_block_definition" "this" {
type = "STRING"
assignment_type = "NONE"
}
+
+ summary = {
+ display_name = "Summary"
+ type = "STRING"
+ assignment_type = "SUMMARY"
+ }
}
permissions = []
diff --git a/modules/stackit/network-area/meshstack_integration.tf b/modules/stackit/network-area/meshstack_integration.tf
index 7a83ace62..9df383730 100644
--- a/modules/stackit/network-area/meshstack_integration.tf
+++ b/modules/stackit/network-area/meshstack_integration.tf
@@ -247,7 +247,7 @@ resource "meshstack_building_block_definition" "this" {
outputs = {
network_area_url = {
- display_name = "Network Area URL"
+ display_name = "Open Network Area"
type = "STRING"
assignment_type = "RESOURCE_URL"
}
diff --git a/modules/stackit/network/meshstack_integration.tf b/modules/stackit/network/meshstack_integration.tf
index 5eb531e7f..e4c9cba44 100644
--- a/modules/stackit/network/meshstack_integration.tf
+++ b/modules/stackit/network/meshstack_integration.tf
@@ -223,7 +223,7 @@ resource "meshstack_building_block_definition" "this" {
outputs = {
network_url = {
- display_name = "Network URL"
+ display_name = "Open Network"
type = "STRING"
assignment_type = "RESOURCE_URL"
}
diff --git a/modules/stackit/project/buildingblock/outputs.tf b/modules/stackit/project/buildingblock/outputs.tf
index 364651427..2c1bb4ff7 100644
--- a/modules/stackit/project/buildingblock/outputs.tf
+++ b/modules/stackit/project/buildingblock/outputs.tf
@@ -14,7 +14,7 @@ output "project_name" {
}
output "project_url" {
- value = "https://portal.stackit.cloud/projects/${stackit_resourcemanager_project.project.project_id}"
+ value = "https://portal.stackit.cloud/dashboard?project=${stackit_resourcemanager_project.project.project_id}"
description = "The deep link URL to access the project in the StackIt portal."
}
@@ -24,7 +24,7 @@ output "summary" {
project_name = stackit_resourcemanager_project.project.name
project_id = stackit_resourcemanager_project.project.project_id
container_id = stackit_resourcemanager_project.project.container_id
- project_url = "https://portal.stackit.cloud/projects/${stackit_resourcemanager_project.project.project_id}"
+ project_url = "https://portal.stackit.cloud/dashboard?project=${stackit_resourcemanager_project.project.project_id}"
unknown_users = local.unknown_users
membership_summary = fileexists("${path.module}/stackit_organization_membership_summary.md") ? file("${path.module}/stackit_organization_membership_summary.md") : "STACKIT organization membership summary was not generated."
})
diff --git a/modules/stackit/server/buildingblock/README.md b/modules/stackit/server/buildingblock/README.md
index 4d3b58411..7fd7a0048 100644
--- a/modules/stackit/server/buildingblock/README.md
+++ b/modules/stackit/server/buildingblock/README.md
@@ -64,6 +64,7 @@ No modules.
|------|-------------|
| [public\_ip](#output\_public\_ip) | Public IP the VM is reachable on over SSH. |
| [server\_id](#output\_server\_id) | ID of the STACKIT server. |
+| [server\_url](#output\_server\_url) | STACKIT portal page of the server. |
| [ssh\_command](#output\_ssh\_command) | Ready-to-use SSH command once the generated private key is on disk (see ssh\_private\_key). |
| [ssh\_private\_key](#output\_ssh\_private\_key) | Generated OpenSSH private key to log into the VM. Save it to a file, chmod 600, and pass it with ssh -i. |
| [ssh\_username](#output\_ssh\_username) | Login user for the VM, as shipped by the chosen image. |
diff --git a/modules/stackit/server/buildingblock/outputs.tf b/modules/stackit/server/buildingblock/outputs.tf
index c901d61f8..48800c21e 100644
--- a/modules/stackit/server/buildingblock/outputs.tf
+++ b/modules/stackit/server/buildingblock/outputs.tf
@@ -19,6 +19,15 @@ output "ssh_private_key" {
description = "Generated OpenSSH private key to log into the VM. Save it to a file, chmod 600, and pass it with ssh -i."
}
+locals {
+ server_url = "https://portal.stackit.cloud/server/servers/${stackit_server.this.server_id}/overview?project=${var.stackit_project_id}"
+}
+
+output "server_url" {
+ value = local.server_url
+ description = "STACKIT portal page of the server."
+}
+
output "server_id" {
value = stackit_server.this.server_id
description = "ID of the STACKIT server."
@@ -29,7 +38,8 @@ output "summary" {
value = <<-EOT
# STACKIT VM `${var.name}`
- Your `${var.machine_type}` VM is up at **`${stackit_public_ip.this.ip}`**.
+ Your `${var.machine_type}` VM is up at **`${stackit_public_ip.this.ip}`**
+ ([Open in STACKIT Portal](${local.server_url})).
Log in with the generated key (from the **SSH Private Key** output):
diff --git a/modules/stackit/server/meshstack_integration.tf b/modules/stackit/server/meshstack_integration.tf
index 6c634e5ab..c4fd0832b 100644
--- a/modules/stackit/server/meshstack_integration.tf
+++ b/modules/stackit/server/meshstack_integration.tf
@@ -353,6 +353,12 @@ resource "meshstack_building_block_definition" "this" {
assignment_type = "NONE"
}
+ server_url = {
+ display_name = "Open Server"
+ type = "STRING"
+ assignment_type = "RESOURCE_URL"
+ }
+
summary = {
display_name = "Summary"
type = "STRING"
diff --git a/modules/stackit/service-account-federation/buildingblock/README.md b/modules/stackit/service-account-federation/buildingblock/README.md
index 22e015d08..b784dc4c1 100644
--- a/modules/stackit/service-account-federation/buildingblock/README.md
+++ b/modules/stackit/service-account-federation/buildingblock/README.md
@@ -71,5 +71,6 @@ No modules.
| Name | Description |
|------|-------------|
| [service\_account\_email](#output\_service\_account\_email) | Email of the federated service account. Children of this block authenticate as it. |
+| [service\_account\_url](#output\_service\_account\_url) | STACKIT portal page of the federated service account. |
| [summary](#output\_summary) | Summary of the federation. |
diff --git a/modules/stackit/service-account-federation/buildingblock/SUMMARY.md.tftpl b/modules/stackit/service-account-federation/buildingblock/SUMMARY.md.tftpl
index 3eb08e445..b64dd9187 100644
--- a/modules/stackit/service-account-federation/buildingblock/SUMMARY.md.tftpl
+++ b/modules/stackit/service-account-federation/buildingblock/SUMMARY.md.tftpl
@@ -9,4 +9,4 @@ Runs of these building block definitions act as the service account through work
_None._
%{ endif ~}
-[Open service accounts](https://portal.stackit.cloud/projects/${project_id}/service-accounts)
+[Open service account](${service_account_url})
diff --git a/modules/stackit/service-account-federation/buildingblock/outputs.tf b/modules/stackit/service-account-federation/buildingblock/outputs.tf
index 8f81b1034..1c224abf5 100644
--- a/modules/stackit/service-account-federation/buildingblock/outputs.tf
+++ b/modules/stackit/service-account-federation/buildingblock/outputs.tf
@@ -3,11 +3,20 @@ output "service_account_email" {
description = "Email of the federated service account. Children of this block authenticate as it."
}
+locals {
+ service_account_url = "https://portal.stackit.cloud/service-accounts/${var.service_account_email}/overview?project=${var.project_id}"
+}
+
+output "service_account_url" {
+ value = local.service_account_url
+ description = "STACKIT portal page of the federated service account."
+}
+
output "summary" {
description = "Summary of the federation."
value = templatefile("${path.module}/SUMMARY.md.tftpl", {
service_account_email = var.service_account_email
- project_id = var.project_id
+ service_account_url = local.service_account_url
definitions = var.federated_building_block_definitions
})
}
diff --git a/modules/stackit/service-account-federation/meshstack_integration.tf b/modules/stackit/service-account-federation/meshstack_integration.tf
index c9f05b53c..3d5011188 100644
--- a/modules/stackit/service-account-federation/meshstack_integration.tf
+++ b/modules/stackit/service-account-federation/meshstack_integration.tf
@@ -219,6 +219,12 @@ resource "meshstack_building_block_definition" "this" {
assignment_type = "NONE"
}
+ service_account_url = {
+ display_name = "Open Service Account"
+ type = "STRING"
+ assignment_type = "RESOURCE_URL"
+ }
+
summary = {
display_name = "Summary"
type = "STRING"
diff --git a/modules/stackit/service-account/buildingblock/README.md b/modules/stackit/service-account/buildingblock/README.md
index 9b2120d23..a30c4caf1 100644
--- a/modules/stackit/service-account/buildingblock/README.md
+++ b/modules/stackit/service-account/buildingblock/README.md
@@ -50,6 +50,6 @@ No modules.
|------|-------------|
| [service\_account\_email](#output\_service\_account\_email) | Email of the created STACKIT service account. Use it as the principal in external federation configs and STACKIT role assignments. |
| [service\_account\_id](#output\_service\_account\_id) | Id of the created STACKIT service account. Forms asking for a service account often want this next to the email, e.g. linking a Harbor robot. |
-| [service\_account\_url](#output\_service\_account\_url) | Deep link to the service accounts overview of the project in the STACKIT portal. |
+| [service\_account\_url](#output\_service\_account\_url) | Deep link to the service account in the STACKIT portal. |
| [summary](#output\_summary) | Summary of the created service account. |
diff --git a/modules/stackit/service-account/buildingblock/SUMMARY.md.tftpl b/modules/stackit/service-account/buildingblock/SUMMARY.md.tftpl
index 7533812d4..eb8d58f6b 100644
--- a/modules/stackit/service-account/buildingblock/SUMMARY.md.tftpl
+++ b/modules/stackit/service-account/buildingblock/SUMMARY.md.tftpl
@@ -7,4 +7,4 @@
| **Email** | `${service_account_email}` |
| **Project** | `${project_id}` |
| **Roles** | ${join(", ", roles)} |
-| **Portal** | [Open service accounts](https://portal.stackit.cloud/projects/${project_id}/service-accounts) |
+| **Portal** | [Open service account](https://portal.stackit.cloud/service-accounts/${service_account_email}/overview?project=${project_id}) |
diff --git a/modules/stackit/service-account/buildingblock/outputs.tf b/modules/stackit/service-account/buildingblock/outputs.tf
index e95f05db5..dc6710a9a 100644
--- a/modules/stackit/service-account/buildingblock/outputs.tf
+++ b/modules/stackit/service-account/buildingblock/outputs.tf
@@ -9,8 +9,8 @@ output "service_account_id" {
}
output "service_account_url" {
- value = "https://portal.stackit.cloud/projects/${var.project_id}/service-accounts"
- description = "Deep link to the service accounts overview of the project in the STACKIT portal."
+ value = "https://portal.stackit.cloud/service-accounts/${stackit_service_account.this.email}/overview?project=${var.project_id}"
+ description = "Deep link to the service account in the STACKIT portal."
}
output "summary" {
diff --git a/modules/stackit/service-account/meshstack_integration.tf b/modules/stackit/service-account/meshstack_integration.tf
index beb45bb23..33eacdcd2 100644
--- a/modules/stackit/service-account/meshstack_integration.tf
+++ b/modules/stackit/service-account/meshstack_integration.tf
@@ -235,7 +235,7 @@ resource "meshstack_building_block_definition" "this" {
outputs = {
service_account_url = {
- display_name = "Service Account URL"
+ display_name = "Open Service Account"
type = "STRING"
assignment_type = "RESOURCE_URL"
}
diff --git a/modules/stackit/stackit-project-starterkit/buildingblock/this/outputs.tf b/modules/stackit/stackit-project-starterkit/buildingblock/this/outputs.tf
index 3248f0e45..412559d9f 100644
--- a/modules/stackit/stackit-project-starterkit/buildingblock/this/outputs.tf
+++ b/modules/stackit/stackit-project-starterkit/buildingblock/this/outputs.tf
@@ -14,7 +14,7 @@ output "project_identifier" {
output "stackit_project_url" {
description = "Deep link to the created STACKIT project, once the tenant has replicated."
- value = local.stackit_project_id == null ? "https://portal.stackit.cloud" : "https://portal.stackit.cloud/projects/${local.stackit_project_id}"
+ value = local.stackit_project_id == null ? "https://portal.stackit.cloud" : "https://portal.stackit.cloud/dashboard?project=${local.stackit_project_id}"
}
# `n/a` rather than null when there is no network. A declared building block definition output has to
@@ -41,7 +41,7 @@ output "summary" {
)
stackit_project = (local.stackit_project_id == null
? "provisioning — the STACKIT Project building block is still running"
- : "[Open in STACKIT Portal](https://portal.stackit.cloud/projects/${local.stackit_project_id}) (`${local.stackit_project_id}`)"
+ : "[Open in STACKIT Portal](https://portal.stackit.cloud/dashboard?project=${local.stackit_project_id}) (`${local.stackit_project_id}`)"
)
project_admin = local.creator_is_user ? var.creator.displayName : "not assigned — the creator is a ${var.creator.type}, which has no user to bind"
})
diff --git a/reference-architectures/stackit-kubernetes/IMPLEMENTATION_DETAILS.md b/reference-architectures/stackit-kubernetes/IMPLEMENTATION_DETAILS.md
index 62feab1e6..fe157e042 100644
--- a/reference-architectures/stackit-kubernetes/IMPLEMENTATION_DETAILS.md
+++ b/reference-architectures/stackit-kubernetes/IMPLEMENTATION_DETAILS.md
@@ -11,11 +11,13 @@ no STACKIT identity and only bootstraps, in one apply:
`editor` and `iam.member-admin` (the registry block grants members their role with it),
3. the **Bootstrap Identity Federation**, its child, trusting only the nested **STACKIT
Kubernetes Platform Services** definition,
-4. one **Platform Services** building block, with the account's email as a known input.
+4. one **STACKIT Kubernetes Platform Services** building block, with the account's email as a
+ known input.
-Platform Services acts as the account from its first run and orders the tree. The outer run waits
-for it and shows its summary. Every outer input the tree needs reaches it as a Platform Services
-input, so updating the outer building block is the only way to change the platform.
+STACKIT Kubernetes Platform Services acts as the account from its first run and orders the tree.
+The outer run waits for it and shows its summary. Every outer input the tree needs reaches it as a
+STACKIT Kubernetes Platform Services input, so updating the outer building block is the only way
+to change the platform.
## Federations and parents
diff --git a/reference-architectures/stackit-kubernetes/README.md b/reference-architectures/stackit-kubernetes/README.md
index fb1996879..e06c66628 100644
--- a/reference-architectures/stackit-kubernetes/README.md
+++ b/reference-architectures/stackit-kubernetes/README.md
@@ -51,12 +51,14 @@ platform's STACKIT Secrets Manager.
### Platform team
-The platform team orders the **STACKIT Kubernetes Platform** building block once. The definitions
-it registers and what each provisions:
+The platform team orders the **STACKIT Kubernetes Platform** building block. The definitions it
+registers, and what each one provisions in the STACKIT project, which is the meshTenant of the
+**STACKIT Kubernetes Platform** meshProject on the landing zone's STACKIT meshPlatform:

-The building blocks Platform Services orders, with their parents as meshPanel shows them:
+The same building blocks as a tree, with an arrow from each parent to its child as meshPanel shows
+them. The frames show which run orders them:

@@ -116,6 +118,10 @@ Once set up, it triggers the repository's workflow and waits for it to finish.
STACKIT project is not protected against deletion. It cannot be changed after the order.
3. Finish phase 2.
+The definition's `display_name_template` names the building block `STACKIT Kubernetes Platform
+` in the order form. A foundation that orders it through the meshStack
+provider sets `display_name` to the same string.
+
The main inputs:
| Input | Purpose |
@@ -142,7 +148,7 @@ otherwise no tenant can be created on the landing zone.
**Overrides** replaces generated values, each optional: `cluster_name`, `cluster_issuer_email` (the
platform's service account by default) and `project_identifier` (`-ske` by
default). The building block outputs a link to the STACKIT project
-and the summary of Platform Services.
+and the summary of STACKIT Kubernetes Platform Services.
### Phase 2: link the Harbor robot
@@ -174,6 +180,47 @@ export VAULT_TOKEN=$(stackit auth get-access-token)
vault kv list -mount=/
```
+## Shipping the Starter Kit to Application Teams
+
+The [application-team diagram](#application-teams) shows the three definitions and what an order
+creates.
+
+**Who registers it.** The nested **STACKIT Kubernetes Platform Services** run registers the three
+definitions application teams use, in `platform/buildingblock/starterkit.tf`. It does so only in phase 2, once
+**Phase 2 Completed** is true, because their pipelines push images and their workloads pull them
+with the robots phase 2 mints:
+
+1. **STACKIT Git Repository** creates a repository in the platform's Forgejo organization and
+ reads the Forgejo token and the push robot from the Secrets Manager. The **Starter Kit Identity
+ Federation** lets its runs act as the platform's service account.
+2. **SKE Forgejo Connector** wires a repository to one namespace. It reads the admin kubeconfig,
+ the Forgejo token, the pull robot and the `stackit-ai` secret, and takes the repository from
+ the Git Repository building block it is a child of.
+3. **SKE Starterkit** orders the other two. It gets the SKE platform, one landing zone per stage,
+ the DNS zone, the template repository and the version of each of the other two definitions.
+
+**Where it lives.** All three are owned by the workspace that ordered **STACKIT Kubernetes
+Platform**, next to the platform's own definitions. They carry the `starterkit_building_block`
+tags and the **Starterkit Approval Policies**.
+
+**When it is released.** `hub.bbd_draft` of this definition decides it, and only for these three.
+With `true`, every STACKIT Kubernetes Platform Services run updates their draft version, and only
+the owning workspace can order them. Use it to try a Hub ref. With `false`, the run releases them,
+and application teams see **SKE Starterkit** in the marketplace. A new Hub ref takes two runs: one
+with `true` adds a draft version on top of the released one, one with `false` releases it.
+Existing starter kits stay on their version until they are upgraded.
+
+**How an application team orders it.** The team orders **SKE Starterkit** in its own workspace
+and gives a project name and a payment method. The run creates:
+
+- the Git repository, cloned from the template repository,
+- one project per stage, each with a tenant on that stage's landing zone,
+- one Forgejo Connector per tenant, which runs the pipeline for its stage,
+- a Project Admin binding on every project for the user who ordered it.
+
+The app is served at `.` for `prod` and at `-.` for the
+other stages. The starter kit's summary links each stage and the repository.
+
## Adopting Existing Resources
The optional **Imports** input takes over resources that already exist, for example a platform
@@ -181,7 +228,7 @@ built before this architecture:
| Key | Takes over |
|---|---|
-| `project = {display_name, admin_bindings}` | the meshProject `overrides.project_identifier` and its tenant on the landing zone platform; `display_name` keeps the project's display name, `admin_bindings` maps a username to the name of its existing Project Admin binding |
+| `project = {admin_bindings}` | the meshProject `overrides.project_identifier` and its tenant on the landing zone platform, renamed to `STACKIT Kubernetes Platform `; `admin_bindings` maps a username to the name of its existing Project Admin binding |
| `adopt_ske = true` | the SKE cluster `overrides.cluster_name` |
| `git = {instance_id, instance_name, forgejo_organization}` | the STACKIT Git instance and its Forgejo organization |
| `dns = {zone_id, record_set_id}` | the DNS zone **DNS Domain** names and its wildcard record set |
diff --git a/reference-architectures/stackit-kubernetes/buildingblock/README.md b/reference-architectures/stackit-kubernetes/buildingblock/README.md
index b0473cc45..a07397beb 100644
--- a/reference-architectures/stackit-kubernetes/buildingblock/README.md
+++ b/reference-architectures/stackit-kubernetes/buildingblock/README.md
@@ -8,7 +8,7 @@ architecture. It holds no STACKIT credential and declares no `stackit` provider.
- the **Automation Identity**, from the landing zone's STACKIT Service Account definition,
- the **Bootstrap Identity Federation**, its child, trusting only the nested **STACKIT Kubernetes
Platform Services** definition,
-- one **Platform Services** building block from that definition. Its run is in
+- one **STACKIT Kubernetes Platform Services** building block from that definition. Its run is in
[`../platform/buildingblock`](../platform/buildingblock/README.md).
`imports.project` takes over the existing meshProject and its tenant. The architecture and adoption
diff --git a/reference-architectures/stackit-kubernetes/buildingblock/main.tf b/reference-architectures/stackit-kubernetes/buildingblock/main.tf
index 57d6891a2..b3f45cd11 100644
--- a/reference-architectures/stackit-kubernetes/buildingblock/main.tf
+++ b/reference-architectures/stackit-kubernetes/buildingblock/main.tf
@@ -32,10 +32,7 @@ locals {
project_identifier = coalesce(local.overrides.project_identifier, "${local.platform_identifier}-ske")
- project_display_name = coalesce(
- local.imports.project == null ? null : local.imports.project.display_name,
- "STACKIT Kubernetes Platform: ${local.platform_identifier}"
- )
+ project_display_name = "STACKIT Kubernetes Platform ${local.platform_identifier}"
# A production platform deletes what it adopted, like anything it created.
release_imports_on_destroy = var.playground_mode && local.imports.project != null
diff --git a/reference-architectures/stackit-kubernetes/buildingblock/outputs.tf b/reference-architectures/stackit-kubernetes/buildingblock/outputs.tf
index 6e73409d2..a6d35feb8 100644
--- a/reference-architectures/stackit-kubernetes/buildingblock/outputs.tf
+++ b/reference-architectures/stackit-kubernetes/buildingblock/outputs.tf
@@ -1,11 +1,11 @@
output "ske_project_url" {
description = "Deep link to the STACKIT project the SKE cluster and its assets run in."
- value = "https://portal.stackit.cloud/projects/${local.stackit_project_id}"
+ value = "https://portal.stackit.cloud/dashboard?project=${local.stackit_project_id}"
}
output "summary" {
description = "Summary of the platform, as the nested building block reports it."
- # `try` because a Platform Services building block whose run failed has no outputs, and the
- # destroy run that removes it still evaluates this.
+ # `try` because a STACKIT Kubernetes Platform Services building block whose run failed has no
+ # outputs, and the destroy run that removes it still evaluates this.
value = try(jsondecode(meshstack_building_block.platform.status.outputs["summary"].value), "")
}
diff --git a/reference-architectures/stackit-kubernetes/buildingblock/platform.tf b/reference-architectures/stackit-kubernetes/buildingblock/platform.tf
index 2532795ea..ad272a969 100644
--- a/reference-architectures/stackit-kubernetes/buildingblock/platform.tf
+++ b/reference-architectures/stackit-kubernetes/buildingblock/platform.tf
@@ -48,7 +48,7 @@ resource "meshstack_building_block" "platform" {
spec = {
parent_building_block_refs = [meshstack_building_block.bootstrap_federation.ref]
building_block_definition_version_ref = module.platform_integration.building_block_definition.version_ref
- display_name = "Platform Services"
+ display_name = "STACKIT Kubernetes Platform Services ${local.platform_identifier}"
target_ref = module.tenant.tenant.ref
inputs = {
diff --git a/reference-architectures/stackit-kubernetes/buildingblock/variables.tf b/reference-architectures/stackit-kubernetes/buildingblock/variables.tf
index d7b645fee..27175f332 100644
--- a/reference-architectures/stackit-kubernetes/buildingblock/variables.tf
+++ b/reference-architectures/stackit-kubernetes/buildingblock/variables.tf
@@ -209,7 +209,6 @@ variable "starterkit_approval_policies" {
variable "imports" {
type = object({
project = optional(object({
- display_name = optional(string)
admin_bindings = optional(map(string), {})
}))
adopt_ske = optional(bool, false)
diff --git a/reference-architectures/stackit-kubernetes/e2e/main.tf b/reference-architectures/stackit-kubernetes/e2e/main.tf
index eebfc7698..0cd58b0c3 100644
--- a/reference-architectures/stackit-kubernetes/e2e/main.tf
+++ b/reference-architectures/stackit-kubernetes/e2e/main.tf
@@ -120,7 +120,7 @@ resource "meshstack_building_block" "this" {
spec = {
building_block_definition_version_ref = { uuid = module.stackit_kubernetes.building_block_definition.version_ref.uuid }
- display_name = "${var.test_context.run_id} STACKIT Kubernetes Platform"
+ display_name = "${var.test_context.run_id} STACKIT Kubernetes Platform ${var.test_context.run_id}"
target_ref = {
kind = "meshWorkspace"
name = var.test_context.workspace
diff --git a/reference-architectures/stackit-kubernetes/meshstack_integration.tf b/reference-architectures/stackit-kubernetes/meshstack_integration.tf
index e6996cfc4..a2c2d3eda 100644
--- a/reference-architectures/stackit-kubernetes/meshstack_integration.tf
+++ b/reference-architectures/stackit-kubernetes/meshstack_integration.tf
@@ -111,7 +111,7 @@ resource "meshstack_building_block_definition" "this" {
spec = {
display_name = "STACKIT Kubernetes Platform Reference Architecture"
- display_name_template = "STACKIT Kubernetes Platform"
+ display_name_template = "STACKIT Kubernetes Platform {{platform_identifier}}"
symbol = "https://raw.githubusercontent.com/meshcloud/meshstack-hub/${var.hub.git_ref}/reference-architectures/stackit-kubernetes/buildingblock/logo.png"
description = "Bootstraps a Kubernetes platform on STACKIT on top of a STACKIT Landing Zone: SKE cluster, in-cluster platform services and the meshStack SKE platform with one landing zone per stage."
support_url = "https://portal.stackit.cloud/ske"
@@ -128,8 +128,8 @@ resource "meshstack_building_block_definition" "this" {
- **Hosting project** – a STACKIT project (self-hosted as a meshStack tenant) that the cluster
runs in.
- **Service account** – ordered from the landing zone's STACKIT Service Account definition.
- - **Platform Services** – one nested building block that orders everything below as the service
- account.
+ - **STACKIT Kubernetes Platform Services** – one nested building block that orders everything
+ below as the service account.
- **Secrets Manager** – a STACKIT Secrets Manager instance that holds every credential the
building blocks below create, so no building block shows one as an output.
- **SKE cluster** – a managed STACKIT Kubernetes Engine cluster with an admin kubeconfig.
@@ -523,7 +523,6 @@ resource "meshstack_building_block_definition" "this" {
description = "Adopt the hosting project. Every other import except starterkit_definitions needs it."
additionalProperties = false
properties = {
- display_name = { type = "string", title = "display_name" }
admin_bindings = { type = "object", title = "admin_bindings", description = "Existing Project Admin binding names by username.", additionalProperties = { type = "string" } }
}
}
diff --git a/reference-architectures/stackit-kubernetes/platform/buildingblock/SUMMARY.md.tftpl b/reference-architectures/stackit-kubernetes/platform/buildingblock/SUMMARY.md.tftpl
index bd69eb977..d99c12aa6 100644
--- a/reference-architectures/stackit-kubernetes/platform/buildingblock/SUMMARY.md.tftpl
+++ b/reference-architectures/stackit-kubernetes/platform/buildingblock/SUMMARY.md.tftpl
@@ -11,7 +11,7 @@
| Property | Value |
|----------|-------|
-| **STACKIT Project** | [Open in STACKIT Portal](https://portal.stackit.cloud/projects/${stackit_project_id}) (`${stackit_project_id}`) |
+| **STACKIT Project** | [Open in STACKIT Portal](https://portal.stackit.cloud/dashboard?project=${stackit_project_id}) (`${stackit_project_id}`) |
| **Secrets Manager** | [Open in STACKIT Portal](https://portal.stackit.cloud/secrets-manager/instances/${secrets_manager_instance_id}/overview?project=${stackit_project_id}) |
| **SKE Cluster** | `${cluster_name}` — @buildingblock[${cluster_bb_uuid}] |
| **Ingress & Certificates** | `${ingress_load_balancer_ip}` — @buildingblock[${ingress_bb_uuid}] |
diff --git a/reference-architectures/stackit-kubernetes/platform/meshstack_integration.tf b/reference-architectures/stackit-kubernetes/platform/meshstack_integration.tf
index 5ccc22ecd..862074945 100644
--- a/reference-architectures/stackit-kubernetes/platform/meshstack_integration.tf
+++ b/reference-architectures/stackit-kubernetes/platform/meshstack_integration.tf
@@ -78,7 +78,7 @@ resource "meshstack_building_block_definition" "this" {
spec = {
display_name = "STACKIT Kubernetes Platform Services"
- display_name_template = "Platform Services"
+ display_name_template = "STACKIT Kubernetes Platform Services"
symbol = "https://raw.githubusercontent.com/meshcloud/meshstack-hub/${var.hub.git_ref}/reference-architectures/stackit-kubernetes/buildingblock/logo.png"
description = "The nested part of the STACKIT Kubernetes Platform: the SKE cluster, its platform services and the meshStack SKE platform, ordered by the STACKIT Kubernetes Platform building block."
support_url = "https://portal.stackit.cloud/ske"
diff --git a/reference-architectures/stackit-kubernetes/stackit-kubernetes-app-team.dot b/reference-architectures/stackit-kubernetes/stackit-kubernetes-app-team.dot
index 428183eb2..e5eb0d2bf 100644
--- a/reference-architectures/stackit-kubernetes/stackit-kubernetes-app-team.dot
+++ b/reference-architectures/stackit-kubernetes/stackit-kubernetes-app-team.dot
@@ -41,12 +41,18 @@ digraph stackit_kubernetes_app_team {
margin=18
REPO [label="🔀 App repository ×1\ncloned from the template" fillcolor="#e5f2ea" color="#85bfa0"]
- PROJ [label="🗂️ Project ×N\none per stage" fillcolor="#e5f2ea" color="#85bfa0"]
- ACT [label="⚙️ Forgejo Actions ×N\none pipeline per stage" fillcolor="#e5f2ea" color="#85bfa0"]
- NS [label="☸️ Namespace ×N\none per stage" fillcolor="#e5f2ea" color="#85bfa0"]
+ PROJ [label="🗂️ Project ×{dev,prod,…}" fillcolor="#e5f2ea" color="#85bfa0"]
+ ACT [label="⚙️ Forgejo Actions ×{dev,prod,…}" fillcolor="#e5f2ea" color="#85bfa0"]
+ subgraph cluster_ns {
+ label="☸️ Namespace ×{dev,prod,…}"
+ fontname="Helvetica" fontsize=11
+ style="rounded,filled" fillcolor="#e5f2ea" color="#85bfa0"
+ margin=12
+
+ NS [label="🤖 ai-summarizer\ndemo Deployment" fillcolor="#ffffff" color="#85bfa0"]
+ }
{ rank=same; REPO -> PROJ [style=invis] }
- { rank=same; ACT -> NS [style=invis] }
REPO -> ACT [xlabel="runs pipeline"]
PROJ -> NS [xlabel="tenant namespace"]
@@ -62,7 +68,7 @@ digraph stackit_kubernetes_app_team {
HARBOR [label="🗄️ Container Registry\nHarbor project per platform" fillcolor="#eef2f6" color="#93a7bb"]
MODEL [label="🧠 Model Serving\nLLM API" fillcolor="#eef2f6" color="#93a7bb"]
ING [label="🔐 Ingress + cert-manager\nHAProxy LoadBalancer" fillcolor="#eef2f6" color="#93a7bb"]
- DNS [label="🧭 DNS zone\nwildcard hostname" fillcolor="#eef2f6" color="#93a7bb"]
+ DNS [label="🧭 DNS zone" fillcolor="#eef2f6" color="#93a7bb"]
{ rank=same; HARBOR -> MODEL -> ING -> DNS [style=invis] }
}
diff --git a/reference-architectures/stackit-kubernetes/stackit-kubernetes-app-team.svg b/reference-architectures/stackit-kubernetes/stackit-kubernetes-app-team.svg
index a4dbad199..60023eaa6 100644
--- a/reference-architectures/stackit-kubernetes/stackit-kubernetes-app-team.svg
+++ b/reference-architectures/stackit-kubernetes/stackit-kubernetes-app-team.svg
@@ -1,186 +1,187 @@
-