From 0b03d66ce450e6801c2ea642df56906ee97a852d Mon Sep 17 00:00:00 2001 From: Stanislav Kopp Date: Thu, 16 Apr 2026 11:11:19 +0200 Subject: [PATCH 1/2] Add more scripts plus README for each script --- README.md | 4 + scripts/README.md | 141 +++++++++++++++++++++++ scripts/ske-show-versions.sh | 86 ++++++++++++++ scripts/smctl.sh | 215 +++++++++++++++++++++++++++++++++++ 4 files changed, 446 insertions(+) create mode 100644 scripts/README.md create mode 100755 scripts/ske-show-versions.sh create mode 100755 scripts/smctl.sh diff --git a/README.md b/README.md index 97b25cd..e75144a 100644 --- a/README.md +++ b/README.md @@ -22,6 +22,10 @@ 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 + +- [`scripts/`](./scripts/README.md) — helper scripts for working with STACKIT services. + ## How to Use This Repository Please treat this repository as a **developer playground and a resource library** to get working examples. diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 0000000..76e44ca --- /dev/null +++ b/scripts/README.md @@ -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 ` — 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 + +# 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 ` | Print the value of a single key. | +| `get all` | Print all keys as `key: value`. | +| `get all-export` | Print all keys as `export key=value` (suitable for `eval`/`source`). | +| `put [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 ` | 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 +``` diff --git a/scripts/ske-show-versions.sh b/scripts/ske-show-versions.sh new file mode 100755 index 0000000..a200b6f --- /dev/null +++ b/scripts/ske-show-versions.sh @@ -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: \(.)"' diff --git a/scripts/smctl.sh b/scripts/smctl.sh new file mode 100755 index 0000000..86504a8 --- /dev/null +++ b/scripts/smctl.sh @@ -0,0 +1,215 @@ +#!/usr/bin/env bash +# Unified Secret Manager utility for STACKIT Secret Manager (Vault v2) +# Usage: smkey.sh [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 < [args] + +Commands: + get Get a specific secret value + Example: $0 get postgresql db_password + + get all Get all key-value pairs in "key: value" format + Example: $0 get postgresql all + + get all-export Get all key-value pairs in "export key=value" format + Example: $0 get postgresql all-export + + put [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 and arguments" + echo "Usage: $0 get " + echo " $0 get all # Get all keys in 'key: value' format" + echo " $0 get 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 and arguments, with optional " + echo "Usage: $0 put # Provide value directly" + echo " $0 put < 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 -- 2.49.1 From 725fe420e9418a7d4be5c157e0cdbb9d0860bacc Mon Sep 17 00:00:00 2001 From: Mauritz Uphoff Date: Thu, 16 Apr 2026 11:39:02 +0200 Subject: [PATCH 2/2] chore(docs): improve contents section Signed-off-by: Mauritz Uphoff --- README.md | 4 +++- scripts/README.md | 36 ++++++++++++++++++------------------ scripts/ske-show-versions.sh | 8 ++++---- 3 files changed, 25 insertions(+), 23 deletions(-) diff --git a/README.md b/README.md index e75144a..46e3b56 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,9 @@ Let's be upfront about how this repository is maintained: ## Contents -- [`scripts/`](./scripts/README.md) — helper scripts for working with STACKIT services. +- [`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 diff --git a/scripts/README.md b/scripts/README.md index 76e44ca..ab267d2 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -6,12 +6,12 @@ Helper scripts for working with STACKIT services. ## 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` | +| 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` | --- @@ -93,23 +93,23 @@ Wrapper around the `vault` CLI that targets the STACKIT Secret Manager endpoint ### Required environment variables -| Variable | Description | -|----------|-------------| -| `SM_USERNAME` | STACKIT Secret Manager username | -| `SM_PASSWORD` | STACKIT Secret Manager password | +| 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 ` | Print the value of a single key. | -| `get all` | Print all keys as `key: value`. | -| `get all-export` | Print all keys as `export key=value` (suitable for `eval`/`source`). | +| Command | Description | +| -------------------------------- | --------------------------------------------------------------------------------------------------- | +| `get ` | Print the value of a single key. | +| `get all` | Print all keys as `key: value`. | +| `get all-export` | Print all keys as `export key=value` (suitable for `eval`/`source`). | | `put [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 ` | List all keys at a specific path. | -| `help` | Show usage. | +| `list` | List all secret paths under the mount. | +| `list ` | List all keys at a specific path. | +| `help` | Show usage. | ### Examples diff --git a/scripts/ske-show-versions.sh b/scripts/ske-show-versions.sh index a200b6f..3107df0 100755 --- a/scripts/ske-show-versions.sh +++ b/scripts/ske-show-versions.sh @@ -17,10 +17,10 @@ if ! command -v stackit &> /dev/null; then 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]; +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; -- 2.49.1