Skip to content

Construct a compliant clouds.yaml for the helper chart. #9

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
May 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 55 additions & 7 deletions 04-cloud-secret.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,27 +11,75 @@ else
fi
# Read settings -- make sure you can trust it
source "$SET"
# Read helper
THISDIR=$(dirname 0)
source "$THISDIR/yaml_parse.sh"

# Create namespace
test -n "$CS_NAMESPACE"
kubectl create namespace "$CS_NAMESPACE" || true
# Default clouds.yaml location
CLOUDS_YAML=${CLOUDS_YAML:-~/.config/openstack/clouds.yaml}
# Use csp helper chart to create cloud secret
# Notes on expected clouds.yaml:
# - It should have the secrets (which you often keep in secure.yaml instead) merged into it
# NOTE: This will only work if the auth section is the last entry for this cloud in clouds.yaml
# - The cloud should be called openstack
# - We will detect a cacert in there and pass it to the helper chart
if ! test -r "$CLOUDS_YAML"; then echo "clouds.yaml $CLOUDS_YAML not readable"; exit 2; fi
CA=$(grep -A12 "^\s\s*$OS_CLOUD:\s*\$" $CLOUDS_YAML | grep '^\s*cacert:' | head -n1 | tr -d '"' | sed 's/^\s*cacert: *//')
# This would be the safe way using yq:
# CA=$(yq -y < $CLOUDS_YAML '.clouds."'$OS_CLOUD'".cacert' | head -n1); if test "$CA" = "null"; then unset CA; fi
CA=$(RMVTREE=1 extract_yaml clouds.$OS_CLOUD.cacert <$CLOUDS_YAML | sed 's/^\s*cacert: //' || true)
OS_CACERT="${OS_CACERT:-$CA}"
# Extract auth parts from secure.yaml if existent, assume same indentation
SEC_YAML="${CLOUDS_YAML%clouds.yaml}secure.yaml"
if test -r "$SEC_YAML"; then SECRETS=$(RMVTREE=1 RMVCOMMENT=1 extract_yaml clouds.$OS_CLOUD.auth < $SEC_YAML || true); fi
if test -n "$SECRETS"; then
echo "# Appending secrets from secure.yaml to clouds.yaml"
fi
# Determine whether we need to add project ID
RAW_CLOUD=$(extract_yaml clouds.$OS_CLOUD <$CLOUDS_YAML)
if ! echo "$RAW_CLOUD" | grep -q '^\s*project_id:' && echo "$RAW_CLOUD" | grep -q '^\s*project_name:'; then
# Need openstack CLI for this
PROJECT_NAME=$(echo "$RAW_CLOUD" | grep '^\s*project_name:' | sed 's/^\s*project_name: //')
PROJECT_ID=$(openstack project show $PROJECT_NAME -c id -f value | tr -d '\r')
INDENT=$(echo "$RAW_CLOUD" | grep '^\s*project_name:' | sed 's/^\(\s*\)project_name:.*$/\1/')
SECRETS=$(echo -en "${INDENT}project_id: $PROJECT_ID\n$SECRETS")
echo "# Appending project_id: $PROJECT_ID to clouds.yaml"
fi
# We need a region_name, add it in
if ! echo "$RAW_CLOUD" | grep -q '^\s*region_name:'; then
# Need openstack CLI for this
REGION=$(openstack region list -c Region -f value | head -n1 | tr -d '\r')
INDENT=$(echo "$RAW_CLOUD" | grep '^\s*auth:' | sed 's/^\(\s*\)auth:.*$/\1/')
export INSERT="${INDENT}region_name: $REGION"
echo "# Inserting region_name: $REGION to clouds.yaml"
fi
# Construct a clouds.yaml (mode 0600):
# - Only extracting one cloud addressed by $OS_CLOUD
# - By merging secrets in from secure.yaml
# - By renaming it to openstack (current CS limitation)
# - By removing cacert setting
# Store it securely in ~/tmp/clouds-$OS_CLOUD.yaml
echo "# Generating ~/tmp/clouds-$OS_CLOUD.yaml ..."
OLD_UMASK=$(umask)
umask 0177
APPEND="$SECRETS" RMVCOMMENT=1 REMOVE=cacert extract_yaml clouds.$OS_CLOUD < $CLOUDS_YAML | sed "s/^\\(\\s*\\)\\($OS_CLOUD\\):/\\1openstack:/" > ~/tmp/clouds-$OS_CLOUD.yaml
umask $OLD_UMASK
# FIXME: We will provide more settings in cluster-settings.env later, hardcode it for now
#if test "$CS_CCMLB=octavia-ovn"; then OCTOVN="--set octavia_ovn=true"; else unset OCTOVN; fi
OCTOVN="--set octavia_ovn=true"
if test -n "$OS_CACERT"; then
echo "Found CA cert file configured to be $OS_CACERT"
if test ! -r "$OS_CACERT"; then echo "... but could not access it. FATAL."; exit 3; fi
echo "# Found CA cert file configured to be \"$OS_CACERT\""
OS_CACERT="$(ls ${OS_CACERT/\~/$HOME})"
# This will error out as needed
# Extract last cert if things get too long (heuristic!)
if test $(wc -l < $OS_CACERT) -gt 200; then
LASTLN=$(tac $OS_CACERT | grep -n '^-----BEGIN CERTIFICATE-----$' | head -n1 | sed 's/^\([0-9]*\):.*/\1/')
CACERT="$(tac $OS_CACERT | head -n $LN | tac)"
else
CACERT="$(cat $OS_CACERT)"
fi
# Call the helm helper chart now
helm upgrade -i openstack-secrets -n "$CS_NAMESPACE" --create-namespace https://github.com/SovereignCloudStack/openstack-csp-helper/releases/latest/download/openstack-csp-helper.tgz -f $CLOUDS_YAML --set cacert="$(cat $OS_CACERT)" $OCTOVN
helm upgrade -i openstack-secrets -n "$CS_NAMESPACE" --create-namespace https://github.com/SovereignCloudStack/openstack-csp-helper/releases/latest/download/openstack-csp-helper.tgz -f ~/tmp/clouds-$OS_CLOUD.yaml --set cacert="$CACERT" $OCTOVN
else
helm upgrade -i openstack-secrets -n "$CS_NAMESPACE" --create-namespace https://github.com/SovereignCloudStack/openstack-csp-helper/releases/latest/download/openstack-csp-helper.tgz -f $CLOUDS_YAML $OCTOVN
helm upgrade -i openstack-secrets -n "$CS_NAMESPACE" --create-namespace https://github.com/SovereignCloudStack/openstack-csp-helper/releases/latest/download/openstack-csp-helper.tgz -f ~/tmp/clouds-$OS_CLOUD.yaml $OCTOVN
fi
3 changes: 2 additions & 1 deletion 06-wait-clusterclass.sh
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ source "$SET"
# ToDo: Wait for cluster class
echo "The clusterclass should exist now"
echo "WARN: Waiting not yet implemented"
sleep 3
set -x
kubectl get clusterclasses -n "$CS_NAMESPACE"
kubectl get images -n "$CS_NAMESPACE"
kubectl get clusterclasses -n "$CS_NAMESPACE"
2 changes: 1 addition & 1 deletion 08-wait-cluster.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@ echo "The cluster should exist now"
echo "WARN: Waiting not yet implemented"
set -x
clusterctl describe cluster -n "$CS_NAMESPACE" $CL_NAME
echo clusterctl get kubeconfig -n "$CS_NAMESPACE" $CL_NAME > "~/.kube/$CS_NAMESPACE.$CL_NAME.yaml"
echo "clusterctl get kubeconfig -n $CS_NAMESPACE $CL_NAME > ~/.kube/config-$CS_NAMESPACE.$CL_NAME"
2 changes: 1 addition & 1 deletion 17-delete-cluster.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ if test -z "$CS_MAINVER"; then echo "Configure CS_MAINVER"; exit 2; fi
if test -z "$CS_VERSION"; then echo "Configure CS_VERSION"; exit 3; fi
if test -z "$CL_PATCHVER"; then echo "Configure CL_PATCHVER"; exit 4; fi
if test -z "$CL_NAME"; then echo "Configure CL_NAME"; exit 5; fi
if test -z "$CL_PODDIDR"; then echo "Configure CL_PODCIDR"; exit 6; fi
if test -z "$CL_PODCIDR"; then echo "Configure CL_PODCIDR"; exit 6; fi
if test -z "$CL_SVCCIDR"; then echo "Configure CL_SVCCIDR"; exit 7; fi
if test -z "$CL_CTRLNODES"; then echo "Configure CL_CTRLNODES"; exit 8; fi
if test -z "$CL_WRKRNODES"; then echo "Configure CL_WRKRNODES"; exit 9; fi
Expand Down
15 changes: 8 additions & 7 deletions cluster-settings-template.env
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,19 @@
# TODO: Check whether we can make this more compatible with v1 KaaS
#
### Per namespace: secrets
# The namespace to keep your CS objects in for a set of clusters
CS_NAMESPACE=cluster
# Location of the clouds.yaml
# The namespace to keep your CS objects for a set of clusters (use e.g. cloud project name)
CS_NAMESPACE=clusterns
# Location of the clouds.yaml (default: ~/.config/openstack/clouds.yaml)
CLOUDS_YAML=~/.config/openstack/clouds.yaml
# Name of the cloud in there -- currently it must be called openstack
# Name of the cloud in there (default: openstack, any name works now)
OS_CLOUD=${OS_CLOUD:-openstack}
### Per cluster stack settings
# Kubernetes Maj.Min, e.g. 1.32 (without leading v)
# Kubernetes Maj.Min, e.g. 1.32 (without leading v), can be left empty (see last line)
CS_MAINVER=
# CS Template version that matches, e.g. v1 or v0-sha.XXXXXXX
# CS Template version that matches, e.g. v1 or v0-sha.XXXXXXX, see table
CS_VERSION=
### Now the per workload cluster settings
# Full K8s Version Maj.Min.Patch, e.g. 1.32.3 (this is per cluster)
# Full K8s Version Maj.Min.Patch, without leading 'v', e.g. 1.32.3 (this is per cluster)
CL_PATCHVER=
# Cluster name
CL_NAME=
Expand All @@ -30,5 +30,6 @@ CL_CTRLNODES=1
# Number of (initial) worker nodes
CL_WRKRNODES=1
### Autofill magic, don't touch
CS_NAMESPACE=${CS_NAMESPACE:-$OS_CLOUD}
CS_MAINVER=${CS_MAINVER:-${CL_PATCHVER%.*}}

139 changes: 139 additions & 0 deletions yaml_parse.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
#!/bin/bash
#
# YAML parser helper
# We do some (incomplete) YAML parsing in bash as helper to 04-cloud-secret.sh
# to extract and construct a conforming clouds.yaml with exactly one
# openstack entry.
# This used to be SLOW by using several grep calls (forks) per line.
# Optimized a bit using three helpers; still going line by line in bash, so expect
# 2s for 1000 lines of clouds.yaml or so.
#
# (c) Kurt Garloff <s7n@garloff.de>, 5/2025
# SPDX-License-Identifier: CC-BY-SA-4.0

# linestart detection
# $1: linestart to look for
# $2: string to search in
startswith()
{
case "$2" in
"$1"*)
return 0;;
esac
return 1
}

# emptyline helper
# $1: line
islineempty()
{
local LN
IFS=" " read LN < <(echo "$1")
if test -z "$LN"; then return 0; else return 1; fi
}

# comment helper
# $1: line
islinecomment()
{
local LN
IFS=" " read LN < <(echo "$1")
if test "${LN:0:1}" = "#"; then return 0; else return 1; fi
}

# Helper: Parse YAML (recursive)
#
# We take two parameters
# $1: The indentation whitespace
# $2: "1" => We want more indentation
# $3-: The keywords
#
# Environment to pass special functions
# $RMVTREE nonempty: Do not output yaml path leading to this section
# $INSERT and $APPEND is text injected in the outputted block (at beginning and end resp.)
# $REMOVE is a tag to filter out
# $RMVCOMMTENT nonempty: Strip comments
#
# Return value: 0 if we found (and output) a block, 1 otherwise
extract_yaml_rec()
{
#echo "DEBUG: Called extract_yaml_rec $@" 1>&2
local previndent="$1"
local more="$2"
#global LNNO
shift 2
if test -n "$1"; then NOTFOUND=1; fi
while IFS="" read line; do
let LNNO+=1
# Ignore empty lines
#if echo "$line" | grep -q '^\s*$'; then continue; fi
if islineempty "$line"; then continue; fi
# First line of new block: We need more indentation ...
if test "$more" = "1"; then
if ! echo "$line" | grep -q "^$previndent\s"; then return; fi
more=$(echo "$line" | sed "s/^$previndent\\(\s*\\)[^\s].*\$/\\1/")
#echo "$previndent$more# $LNNO: New indent level"
fi
# Detect less indentation than wanted, return
#if ! echo "$line" | grep -q "^$previndent$more"; then return; fi
if ! startswith "$previndent$more" "$line"; then return; fi
# Strip comments if requested
#if test -n "$RMVCOMMENT" && echo "$line" | grep -q '^\s*#'; then continue; fi
if test -n "$RMVCOMMENT" && islinecomment "$line"; then continue; fi
# OK, we we have at least the indentation level needed
# 3 cases:
# (a) We are prior to finding the right block, continue searching
# (b1) Found it, no more nesting needed, output till the end
# (b2) Found it, recurse into next keyword
# (c) We idenitfy the end by finding a different keyword
#
# Case b1: No more keywords to look for, we found the previous ones if we
# got here, just output until the less indentation clause above indicates the end
if test -z "$1"; then
#echo "$previndent$more# $LNNO: Outputing block"
#if test -z "$REMOVE" || ! echo "$line" | grep -q "^$previndent$more$REMOVE:"; then
if test -z "$REMOVE" || ! startswith "$previndent$more$REMOVE:" "$line"; then
echo "$line"
fi
continue
fi
# b2: Search for the keyword
#if echo "$line" | grep -q "^$previndent$more$1:"; then
if startswith "$previndent$more$1:" "$line"; then
#echo "$previndent$more# $LNNO: Found keyword $1"
# Output tree unless we suppress it
if test -z "$RMVTREE"; then
echo "$line"
else
# At the leaf, we may hold a value
if test -z "$2"; then
echo "$line" | grep --color=never "^$previndent$more$1: [^\\s]"
fi
fi
shift
# TODO: Reformat INSERT to match
if test -z "$1"; then
NOTFOUND=0
if test -n "$INSERT"; then echo "$INSERT"; fi
fi
extract_yaml_rec "$previndent$more" "1" "$@"
# TODO: Reformat APPEND to match
if test -z "$1" -a -n "$APPEND"; then echo "$APPEND"; fi
# A return here would allows for only one block of a kind
return $NOTFOUND
# Otherwise we would have needed to save "$@" adn restore it here
fi
# a: OK, just continue to search (without the return above, this is also c)
done
return $NOTFOUND
}

# Helper: extract_yaml
# $1: The tag to search for and output (separated by dots)
extract_yaml()
{
LNNO=0
SRCH=($(echo "$1" | sed 's/\./ /g'))
extract_yaml_rec "" "" "${SRCH[@]}"
}