feat/add-more-script-plus-docs #8

Merged
mauritz.uphoff merged 2 commits from feat/add-more-script-plus-docs into main 2026-04-16 09:53:43 +00:00
4 changed files with 448 additions and 0 deletions

View file

@ -22,6 +22,12 @@ Let's be upfront about how this repository is maintained:
- **No Guarantees on Freshness:** We try our best to keep the examples, Terraform modules, and scripts up to date with the latest provider releases and API changes. However, **we cannot guarantee it**. Things move fast in the cloud, and some examples might become outdated over time.
- **Use Your Brain:** Do not blindly copy-paste code from here directly into a production environment.
## Contents
- [`examples/`](./examples) — Example solutions across a variety of STACKIT products.
- [`scripts/`](./scripts/README.md) — Helper scripts for working with STACKIT services.
- [`modules/`](./modules) — Ready-made Terraform modules to simplify your deployments.
## How to Use This Repository
Please treat this repository as a **developer playground and a resource library** to get working examples.

141
scripts/README.md Normal file
View file

@ -0,0 +1,141 @@
# Scripts
Helper scripts for working with STACKIT services.
> **Warning:** These scripts can perform destructive or wide-reaching operations against your STACKIT account (e.g. deleting volumes, overwriting kubeconfigs, writing secrets). Review each script and understand what it does before running it.
## Overview
| Script | Purpose | Required tools |
| ---------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- | ---------------------------------- |
| [`create-kubeconfig-multiple-projects.sh`](#create-kubeconfig-multiple-projectssh) | Generate kubeconfig entries for every SKE cluster across one or more STACKIT projects. | `stackit`, `yq` |
| [`delete-unused-volumes.sh`](#delete-unused-volumessh) | Delete all STACKIT volumes whose status is `AVAILABLE` (i.e. not attached). | `stackit`, `yq` |
| [`ske-show-versions.sh`](#ske-show-versionssh) | Print overview of SKE cluster Kubernetes versions and nodepool image versions, marking deprecated versions. | `stackit` (>= 0.59.0), `jq`, `awk` |
| [`smctl.sh`](#smctlsh) | Unified CLI wrapper around HashiCorp Vault for the STACKIT Secret Manager (KV v2). | `vault`, `jq` |
---
## `create-kubeconfig-multiple-projects.sh`
Logs in to STACKIT, iterates over a hard-coded list of project IDs, lists all SKE clusters in each project and writes a 60-day kubeconfig for every cluster into a single kubeconfig file.
Edit the `projects=( ... )` array near the bottom of the script before running.
### Flags
- `-f, --filepath <path>` — destination kubeconfig path (default: `$HOME/.kube/config`).
- `-h, --help` — show usage.
### Examples
```bash
# Write to default ~/.kube/config (will prompt before overwriting)
./create-kubeconfig-multiple-projects.sh
# Write to a custom location
./create-kubeconfig-multiple-projects.sh -f ~/.kube/stackit-config
```
---
## `delete-unused-volumes.sh`
Lists all STACKIT volumes in the currently configured project and deletes those with status `AVAILABLE`.
Set `DRY_RUN=1` at the top of the script to preview the deletions without executing them.
### Examples
```bash
# Make sure the right project is selected
stackit config set --project-id <project-id>
# Preview what would be deleted (edit DRY_RUN=1 in the script first)
./delete-unused-volumes.sh
# Actually delete unused volumes
./delete-unused-volumes.sh
```
---
## `ske-show-versions.sh`
Prints a tabular overview of SKE clusters across the project IDs configured in the `projectid` variable. For each nodepool it shows the Kubernetes version and the Flatcar machine image version, annotated with `supported` or the `expirationDate` for deprecated versions. Rows containing any deprecated version are highlighted in red.
The script enforces a minimum STACKIT CLI version (`0.59.0`).
Edit the `projectid="..."` variable (space-separated list) before running.
### Example
```bash
./ske-show-versions.sh
```
Sample output:
```
CLUSTER NAME SKE VERSION NODEPOOL FLATCAR VERSION MACHINE TYPE PROJECT ID
------------ ----------- -------- --------------- ------------ ----------
prod-cluster 1.30.4 (supported) default 4081.2.0 (supported) c1.4 90bc91eb-...
legacy-cluster 1.27.9 (exp. 2026-05-01) default 3815.2.5 (exp. 2026-03-15) c1.2 be20ca97-...
Summary:
Total clusters: 2
```
---
## `smctl.sh`
Wrapper around the `vault` CLI that targets the STACKIT Secret Manager endpoint (`https://prod.sm.eu01.stackit.cloud`) and authenticates with userpass.
### Required environment variables
| Variable | Description |
| ------------- | -------------------------------------------------------------- |
| `SM_USERNAME` | STACKIT Secret Manager username |
| `SM_PASSWORD` | STACKIT Secret Manager password |
| `SM_ID` | KV secrets engine mount path (your secret manager instance ID) |
### Commands
| Command | Description |
| -------------------------------- | --------------------------------------------------------------------------------------------------- |
| `get <vault_path> <key>` | Print the value of a single key. |
| `get <vault_path> all` | Print all keys as `key: value`. |
| `get <vault_path> all-export` | Print all keys as `export key=value` (suitable for `eval`/`source`). |
| `put <vault_path> <key> [value]` | Write/update a key. Reads from stdin if no value is given. Existing keys at the path are preserved. |
| `list` | List all secret paths under the mount. |
| `list <vault_path>` | List all keys at a specific path. |
| `help` | Show usage. |
### Examples
```bash
export SM_USERNAME='my-user'
export SM_PASSWORD='my-pass'
export SM_ID='sm-xxxxxxxx'
# List all paths
./smctl.sh list
# List keys at a path
./smctl.sh list postgresql
# Read a single value
./smctl.sh get postgresql db_password
# Dump everything at a path
./smctl.sh get postgresql all
# Load all secrets at a path into the current shell
eval "$(./smctl.sh get postgresql all-export)"
# Write a value directly
./smctl.sh put postgresql db_password 'super-secret'
# Write a value from a file (e.g. a full .env)
./smctl.sh put terraform secret-env < .env
```

86
scripts/ske-show-versions.sh Executable file
View file

@ -0,0 +1,86 @@
#!/bin/bash
export STACKIT_CLI_MIN_MAJOR=0
export STACKIT_CLI_MIN_MINOR=59
export STACKIT_CLI_MIN_PATCH=0
# Script to show SKE cluster versions and nodepool flatcar versions
set -euo pipefail
# Check if stackit command is available
if ! command -v stackit &> /dev/null; then
echo "Error: stackit command not found"
echo "Please install STACKIT CLI from:"
echo "https://github.com/stackitcloud/stackit-cli/blob/main/INSTALLATION.md"
exit 1
fi
# Check if stackit-cli is supported
stackit -v |awk -v maj="$STACKIT_CLI_MIN_MAJOR" -v min="$STACKIT_CLI_MIN_MINOR" -v pat="$STACKIT_CLI_MIN_PATCH" '/Version:/ { { split($2, a, ".");
current_maj = a[1];
current_min = a[2];
current_pat = a[3];
if (current_maj > maj || (current_maj == maj && current_min > min) || (current_maj == maj && current_min == min && current_pat >= pat)) {
print "✅ Version supported (" current_maj "." current_min "." current_pat ")";
exit 0;
} else {
print "❌ STACKIT CLI version not supported. Need at least " maj "." min "." pat;
exit 1;
}
}
}'
# Define project IDs (space-separated)
projectid="xxxxxx yyyyy"
# Collect all clusters from all projects
all_clusters="[]"
echo "Fetching SKE cluster information..."
for pid in $projectid; do
echo "Fetching clusters for project ID: $pid"
clusters=$(stackit ske cluster list -o json --project-id "$pid")
# Merge with existing clusters
all_clusters=$(echo "$all_clusters" "$clusters" | jq -s '.[0] + .[1]')
done
# Print header
printf "\n%-20s %-30s %-20s %-30s %-15s %-40s\n" "CLUSTER NAME" "SKE VERSION" "NODEPOOL" "FLATCAR VERSION" "MACHINE TYPE" "PROJECT ID"
printf "%-20s %-30s %-20s %-30s %-15s %-40s\n" "------------" "-----------" "--------" "---------------" "------------" "----------"
# Parse JSON and display information
for pid in $projectid; do
clusters=$(stackit ske cluster list -o json --project-id "$pid")
echo "$clusters" | jq -r --arg pid "$pid" '.[] |
.name as $cluster_name |
.kubernetes.version as $k8s_version |
.nodepools[] |
[$cluster_name, $k8s_version, .name, .machine.image.version, .machine.type, $pid] |
@tsv' | while IFS=$'\t' read -r cluster ske_version nodepool flatcar_version machine_type project; do
k8s_version_desc=$(stackit ske options kubernetes-versions -o json | jq -r --arg VERSION $ske_version '.kubernetesVersions[] | select(.version == $VERSION) | if .state == "deprecated" then "exp. "+.expirationDate[0:10] else "supported" end ')
k8s_version_state=$(stackit ske options kubernetes-versions -o json | jq -r --arg VERSION $ske_version '.kubernetesVersions[] | select(.version == $VERSION).state')
flatcar_version_desc=$(stackit ske options machine-images -o json | jq -r --arg VERSION $flatcar_version '.machineImages[] | select(.name == "flatcar") | .versions[] | select(.version == $VERSION) | if .state == "deprecated" then "exp. "+.expirationDate[0:10] else "supported" end ' )
flatcar_version_state=$(stackit ske options machine-images -o json | jq -r --arg VERSION $flatcar_version '.machineImages[] | select(.name == "flatcar") | .versions[] | select(.version == $VERSION).state' )
GREEN='\033[0;32m'
RED='\033[0;31m'
NC='\033[0m'
if [[ "$k8s_version_state" == "deprecated" || "$flatcar_version_state" == "deprecated" ]]; then
ROW_COLOR=$RED
else
ROW_COLOR=$GREEN
fi
printf "${ROW_COLOR}%-20s %-30s %-20s %-30s %-15s %-40s${NC}\n" "$cluster" "$ske_version ($k8s_version_desc)" "$nodepool" "$flatcar_version ($flatcar_version_desc) " "$machine_type" "$project"
done
done
echo ""
echo "Summary:"
echo "$all_clusters" | jq -r 'length | "Total clusters: \(.)"'

215
scripts/smctl.sh Executable file
View file

@ -0,0 +1,215 @@
#!/usr/bin/env bash
# Unified Secret Manager utility for STACKIT Secret Manager (Vault v2)
# Usage: smkey.sh <command> [args]
set -euo pipefail
# Check for required dependencies
for cmd in vault jq; do
if ! command -v "$cmd" &> /dev/null; then
echo "Error: Required command '$cmd' is not installed or not in your PATH." >&2
echo "Please install it before running this script." >&2
exit 1
fi
done
export VAULT_ADDR="https://prod.sm.eu01.stackit.cloud"
# Ensure required env variables are set
: "${SM_USERNAME:?Environment variable SM_USERNAME is not set}"
: "${SM_PASSWORD:?Environment variable SM_PASSWORD is not set}"
: "${SM_ID:?Environment variable SM_ID is not set}"
# Function to authenticate and get Vault token
authenticate() {
if [ -z "${VAULT_TOKEN:-}" ]; then
export VAULT_TOKEN=$(vault login --address "${VAULT_ADDR}" -no-store -format=json \
--method=userpass username="${SM_USERNAME}" password="${SM_PASSWORD}" 2>/dev/null | jq -r .auth.client_token)
fi
}
# Function to display usage
usage() {
cat <<EOF
Usage: $0 <command> [args]
Commands:
get <vault_path> <key> Get a specific secret value
Example: $0 get postgresql db_password
get <vault_path> all Get all key-value pairs in "key: value" format
Example: $0 get postgresql all
get <vault_path> all-export Get all key-value pairs in "export key=value" format
Example: $0 get postgresql all-export
put <vault_path> <key> [value] Put a secret value (from argument or stdin)
Example: $0 put postgresql db_password myvalue123
Example: $0 put terraform secret-env < .env
list [vault_path] List all available vault paths (no arg)
or list all keys in a specific vault path
Example: $0 list
Example: $0 list postgresql
help Show this help message
Environment variables required:
SM_USERNAME STACKIT Secret Manager username
SM_PASSWORD STACKIT Secret Manager password
SM_ID KV secrets engine mount path
EOF
exit 1
}
# Command: get
cmd_get() {
if [ $# -ne 2 ]; then
echo "Error: 'get' requires <vault_path> and <key> arguments"
echo "Usage: $0 get <vault_path> <key>"
echo " $0 get <vault_path> all # Get all keys in 'key: value' format"
echo " $0 get <vault_path> all-export # Get all keys in 'export key=value' format"
exit 1
fi
local vault_path="$1"
local secret_key="$2"
authenticate
# Handle special "all" and "all-export" keys
if [ "${secret_key}" = "all" ]; then
# Get all key-value pairs in "key: value" format
local secret_data=$(vault kv get -mount="${SM_ID}" -format=json "${vault_path}" 2>/dev/null)
if [ $? -ne 0 ] || [ -z "${secret_data}" ]; then
echo "Error: No secret found at path '${vault_path}'"
exit 1
fi
# Output in simple "key: value" format
echo "${secret_data}" | jq -r '.data.data | to_entries[] | "\(.key): \(.value)"'
return
fi
if [ "${secret_key}" = "all-export" ]; then
# Get all key-value pairs in "export key=value" format
local secret_data=$(vault kv get -mount="${SM_ID}" -format=json "${vault_path}" 2>/dev/null)
if [ $? -ne 0 ] || [ -z "${secret_data}" ]; then
echo "Error: No secret found at path '${vault_path}'"
exit 1
fi
# Output in "export key=value" format
echo "${secret_data}" | jq -r '.data.data | to_entries[] | "export \(.key)=\(.value)"'
return
fi
# Standard single key retrieval
vault kv get -mount="${SM_ID}" -field="${secret_key}" "${vault_path}"
}
# Command: put
cmd_put() {
if [ $# -lt 2 ] || [ $# -gt 3 ]; then
echo "Error: 'put' requires <vault_path> and <key> arguments, with optional <value>"
echo "Usage: $0 put <vault_path> <key> <value> # Provide value directly"
echo " $0 put <vault_path> <key> < input_file # Read value from file"
exit 1
fi
local vault_path="$1"
local secret_key="$2"
authenticate
# Get current secret data (if exists)
local current_data=$(vault kv get -mount="${SM_ID}" -format=json "${vault_path}" 2>/dev/null | jq -r '.data.data' || echo "{}")
# Read new value from argument or stdin
local new_value
if [ $# -eq 3 ]; then
# Value provided as argument
new_value="$3"
else
# Read value from stdin
new_value=$(cat)
fi
# Merge existing keys with new key/value
local updated_data=$(echo "${current_data}" | jq --arg k "${secret_key}" --arg v "${new_value}" '. + {($k): $v}')
# Build arguments for vault kv put
local put_args=()
while IFS= read -r key; do
local value=$(echo "${updated_data}" | jq -r --arg k "$key" '.[$k]')
put_args+=("${key}=${value}")
done < <(echo "${updated_data}" | jq -r 'keys[]')
# Write merged data back to Vault
vault kv put -mount="${SM_ID}" "${vault_path}" "${put_args[@]}"
}
# Command: list
cmd_list() {
authenticate
# If no path provided, list all vault paths
if [ $# -eq 0 ]; then
echo "Available vault paths:"
vault kv list -mount="${SM_ID}" -format=json | jq -r '.[]' 2>/dev/null || {
echo "Error: Unable to list vault paths or no paths found"
exit 1
}
return
fi
# If path provided, list all keys in that path
if [ $# -ne 1 ]; then
echo "Error: 'list' requires zero or one argument"
echo "Usage: $0 list [vault_path]"
exit 1
fi
local vault_path="$1"
# Get the secret and list all keys
local secret_data=$(vault kv get -mount="${SM_ID}" -format=json "${vault_path}" 2>/dev/null)
if [ $? -ne 0 ] || [ -z "${secret_data}" ]; then
echo "Error: No secret found at path '${vault_path}'"
exit 1
fi
echo "Keys in ${vault_path}:"
echo "${secret_data}" | jq -r '.data.data | keys[]'
}
# Main command dispatcher
if [ $# -eq 0 ]; then
usage
fi
COMMAND="$1"
shift
case "${COMMAND}" in
get)
cmd_get "$@"
;;
put)
cmd_put "$@"
;;
list)
cmd_list "$@"
;;
help|--help|-h)
usage
;;
*)
echo "Error: Unknown command '${COMMAND}'"
echo
usage
;;
esac