Skip to content

Commit 0247fd7

Browse files
authored
Merge pull request #9 from SovereignCloudStack/feat/construct-clouds.yaml
Construct a compliant clouds.yaml for the helper chart.
2 parents d4823bc + bca21f9 commit 0247fd7

6 files changed

Lines changed: 206 additions & 17 deletions

04-cloud-secret.sh

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -11,27 +11,75 @@ else
1111
fi
1212
# Read settings -- make sure you can trust it
1313
source "$SET"
14+
# Read helper
15+
THISDIR=$(dirname 0)
16+
source "$THISDIR/yaml_parse.sh"
17+
1418
# Create namespace
1519
test -n "$CS_NAMESPACE"
1620
kubectl create namespace "$CS_NAMESPACE" || true
21+
# Default clouds.yaml location
22+
CLOUDS_YAML=${CLOUDS_YAML:-~/.config/openstack/clouds.yaml}
1723
# Use csp helper chart to create cloud secret
1824
# Notes on expected clouds.yaml:
1925
# - It should have the secrets (which you often keep in secure.yaml instead) merged into it
26+
# NOTE: This will only work if the auth section is the last entry for this cloud in clouds.yaml
2027
# - The cloud should be called openstack
2128
# - We will detect a cacert in there and pass it to the helper chart
2229
if ! test -r "$CLOUDS_YAML"; then echo "clouds.yaml $CLOUDS_YAML not readable"; exit 2; fi
23-
CA=$(grep -A12 "^\s\s*$OS_CLOUD:\s*\$" $CLOUDS_YAML | grep '^\s*cacert:' | head -n1 | tr -d '"' | sed 's/^\s*cacert: *//')
24-
# This would be the safe way using yq:
25-
# CA=$(yq -y < $CLOUDS_YAML '.clouds."'$OS_CLOUD'".cacert' | head -n1); if test "$CA" = "null"; then unset CA; fi
30+
CA=$(RMVTREE=1 extract_yaml clouds.$OS_CLOUD.cacert <$CLOUDS_YAML | sed 's/^\s*cacert: //' || true)
2631
OS_CACERT="${OS_CACERT:-$CA}"
32+
# Extract auth parts from secure.yaml if existent, assume same indentation
33+
SEC_YAML="${CLOUDS_YAML%clouds.yaml}secure.yaml"
34+
if test -r "$SEC_YAML"; then SECRETS=$(RMVTREE=1 RMVCOMMENT=1 extract_yaml clouds.$OS_CLOUD.auth < $SEC_YAML || true); fi
35+
if test -n "$SECRETS"; then
36+
echo "# Appending secrets from secure.yaml to clouds.yaml"
37+
fi
38+
# Determine whether we need to add project ID
39+
RAW_CLOUD=$(extract_yaml clouds.$OS_CLOUD <$CLOUDS_YAML)
40+
if ! echo "$RAW_CLOUD" | grep -q '^\s*project_id:' && echo "$RAW_CLOUD" | grep -q '^\s*project_name:'; then
41+
# Need openstack CLI for this
42+
PROJECT_NAME=$(echo "$RAW_CLOUD" | grep '^\s*project_name:' | sed 's/^\s*project_name: //')
43+
PROJECT_ID=$(openstack project show $PROJECT_NAME -c id -f value | tr -d '\r')
44+
INDENT=$(echo "$RAW_CLOUD" | grep '^\s*project_name:' | sed 's/^\(\s*\)project_name:.*$/\1/')
45+
SECRETS=$(echo -en "${INDENT}project_id: $PROJECT_ID\n$SECRETS")
46+
echo "# Appending project_id: $PROJECT_ID to clouds.yaml"
47+
fi
48+
# We need a region_name, add it in
49+
if ! echo "$RAW_CLOUD" | grep -q '^\s*region_name:'; then
50+
# Need openstack CLI for this
51+
REGION=$(openstack region list -c Region -f value | head -n1 | tr -d '\r')
52+
INDENT=$(echo "$RAW_CLOUD" | grep '^\s*auth:' | sed 's/^\(\s*\)auth:.*$/\1/')
53+
export INSERT="${INDENT}region_name: $REGION"
54+
echo "# Inserting region_name: $REGION to clouds.yaml"
55+
fi
56+
# Construct a clouds.yaml (mode 0600):
57+
# - Only extracting one cloud addressed by $OS_CLOUD
58+
# - By merging secrets in from secure.yaml
59+
# - By renaming it to openstack (current CS limitation)
60+
# - By removing cacert setting
61+
# Store it securely in ~/tmp/clouds-$OS_CLOUD.yaml
62+
echo "# Generating ~/tmp/clouds-$OS_CLOUD.yaml ..."
63+
OLD_UMASK=$(umask)
64+
umask 0177
65+
APPEND="$SECRETS" RMVCOMMENT=1 REMOVE=cacert extract_yaml clouds.$OS_CLOUD < $CLOUDS_YAML | sed "s/^\\(\\s*\\)\\($OS_CLOUD\\):/\\1openstack:/" > ~/tmp/clouds-$OS_CLOUD.yaml
66+
umask $OLD_UMASK
2767
# FIXME: We will provide more settings in cluster-settings.env later, hardcode it for now
2868
#if test "$CS_CCMLB=octavia-ovn"; then OCTOVN="--set octavia_ovn=true"; else unset OCTOVN; fi
2969
OCTOVN="--set octavia_ovn=true"
3070
if test -n "$OS_CACERT"; then
31-
echo "Found CA cert file configured to be $OS_CACERT"
32-
if test ! -r "$OS_CACERT"; then echo "... but could not access it. FATAL."; exit 3; fi
71+
echo "# Found CA cert file configured to be \"$OS_CACERT\""
72+
OS_CACERT="$(ls ${OS_CACERT/\~/$HOME})"
73+
# This will error out as needed
74+
# Extract last cert if things get too long (heuristic!)
75+
if test $(wc -l < $OS_CACERT) -gt 200; then
76+
LASTLN=$(tac $OS_CACERT | grep -n '^-----BEGIN CERTIFICATE-----$' | head -n1 | sed 's/^\([0-9]*\):.*/\1/')
77+
CACERT="$(tac $OS_CACERT | head -n $LN | tac)"
78+
else
79+
CACERT="$(cat $OS_CACERT)"
80+
fi
3381
# Call the helm helper chart now
34-
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
82+
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
3583
else
36-
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
84+
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
3785
fi

06-wait-clusterclass.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ source "$SET"
1515
# ToDo: Wait for cluster class
1616
echo "The clusterclass should exist now"
1717
echo "WARN: Waiting not yet implemented"
18+
sleep 3
1819
set -x
19-
kubectl get clusterclasses -n "$CS_NAMESPACE"
2020
kubectl get images -n "$CS_NAMESPACE"
21+
kubectl get clusterclasses -n "$CS_NAMESPACE"

08-wait-cluster.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,4 @@ echo "The cluster should exist now"
1616
echo "WARN: Waiting not yet implemented"
1717
set -x
1818
clusterctl describe cluster -n "$CS_NAMESPACE" $CL_NAME
19-
echo clusterctl get kubeconfig -n "$CS_NAMESPACE" $CL_NAME > "~/.kube/$CS_NAMESPACE.$CL_NAME.yaml"
19+
echo "clusterctl get kubeconfig -n $CS_NAMESPACE $CL_NAME > ~/.kube/config-$CS_NAMESPACE.$CL_NAME"

17-delete-cluster.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ if test -z "$CS_MAINVER"; then echo "Configure CS_MAINVER"; exit 2; fi
1616
if test -z "$CS_VERSION"; then echo "Configure CS_VERSION"; exit 3; fi
1717
if test -z "$CL_PATCHVER"; then echo "Configure CL_PATCHVER"; exit 4; fi
1818
if test -z "$CL_NAME"; then echo "Configure CL_NAME"; exit 5; fi
19-
if test -z "$CL_PODDIDR"; then echo "Configure CL_PODCIDR"; exit 6; fi
19+
if test -z "$CL_PODCIDR"; then echo "Configure CL_PODCIDR"; exit 6; fi
2020
if test -z "$CL_SVCCIDR"; then echo "Configure CL_SVCCIDR"; exit 7; fi
2121
if test -z "$CL_CTRLNODES"; then echo "Configure CL_CTRLNODES"; exit 8; fi
2222
if test -z "$CL_WRKRNODES"; then echo "Configure CL_WRKRNODES"; exit 9; fi

cluster-settings-template.env

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,19 @@
55
# TODO: Check whether we can make this more compatible with v1 KaaS
66
#
77
### Per namespace: secrets
8-
# The namespace to keep your CS objects in for a set of clusters
9-
CS_NAMESPACE=cluster
10-
# Location of the clouds.yaml
8+
# The namespace to keep your CS objects for a set of clusters (use e.g. cloud project name)
9+
CS_NAMESPACE=clusterns
10+
# Location of the clouds.yaml (default: ~/.config/openstack/clouds.yaml)
1111
CLOUDS_YAML=~/.config/openstack/clouds.yaml
12-
# Name of the cloud in there -- currently it must be called openstack
12+
# Name of the cloud in there (default: openstack, any name works now)
1313
OS_CLOUD=${OS_CLOUD:-openstack}
1414
### Per cluster stack settings
15-
# Kubernetes Maj.Min, e.g. 1.32 (without leading v)
15+
# Kubernetes Maj.Min, e.g. 1.32 (without leading v), can be left empty (see last line)
1616
CS_MAINVER=
17-
# CS Template version that matches, e.g. v1 or v0-sha.XXXXXXX
17+
# CS Template version that matches, e.g. v1 or v0-sha.XXXXXXX, see table
1818
CS_VERSION=
1919
### Now the per workload cluster settings
20-
# Full K8s Version Maj.Min.Patch, e.g. 1.32.3 (this is per cluster)
20+
# Full K8s Version Maj.Min.Patch, without leading 'v', e.g. 1.32.3 (this is per cluster)
2121
CL_PATCHVER=
2222
# Cluster name
2323
CL_NAME=
@@ -30,5 +30,6 @@ CL_CTRLNODES=1
3030
# Number of (initial) worker nodes
3131
CL_WRKRNODES=1
3232
### Autofill magic, don't touch
33+
CS_NAMESPACE=${CS_NAMESPACE:-$OS_CLOUD}
3334
CS_MAINVER=${CS_MAINVER:-${CL_PATCHVER%.*}}
3435

yaml_parse.sh

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
#!/bin/bash
2+
#
3+
# YAML parser helper
4+
# We do some (incomplete) YAML parsing in bash as helper to 04-cloud-secret.sh
5+
# to extract and construct a conforming clouds.yaml with exactly one
6+
# openstack entry.
7+
# This used to be SLOW by using several grep calls (forks) per line.
8+
# Optimized a bit using three helpers; still going line by line in bash, so expect
9+
# 2s for 1000 lines of clouds.yaml or so.
10+
#
11+
# (c) Kurt Garloff <s7n@garloff.de>, 5/2025
12+
# SPDX-License-Identifier: CC-BY-SA-4.0
13+
14+
# linestart detection
15+
# $1: linestart to look for
16+
# $2: string to search in
17+
startswith()
18+
{
19+
case "$2" in
20+
"$1"*)
21+
return 0;;
22+
esac
23+
return 1
24+
}
25+
26+
# emptyline helper
27+
# $1: line
28+
islineempty()
29+
{
30+
local LN
31+
IFS=" " read LN < <(echo "$1")
32+
if test -z "$LN"; then return 0; else return 1; fi
33+
}
34+
35+
# comment helper
36+
# $1: line
37+
islinecomment()
38+
{
39+
local LN
40+
IFS=" " read LN < <(echo "$1")
41+
if test "${LN:0:1}" = "#"; then return 0; else return 1; fi
42+
}
43+
44+
# Helper: Parse YAML (recursive)
45+
#
46+
# We take two parameters
47+
# $1: The indentation whitespace
48+
# $2: "1" => We want more indentation
49+
# $3-: The keywords
50+
#
51+
# Environment to pass special functions
52+
# $RMVTREE nonempty: Do not output yaml path leading to this section
53+
# $INSERT and $APPEND is text injected in the outputted block (at beginning and end resp.)
54+
# $REMOVE is a tag to filter out
55+
# $RMVCOMMTENT nonempty: Strip comments
56+
#
57+
# Return value: 0 if we found (and output) a block, 1 otherwise
58+
extract_yaml_rec()
59+
{
60+
#echo "DEBUG: Called extract_yaml_rec $@" 1>&2
61+
local previndent="$1"
62+
local more="$2"
63+
#global LNNO
64+
shift 2
65+
if test -n "$1"; then NOTFOUND=1; fi
66+
while IFS="" read line; do
67+
let LNNO+=1
68+
# Ignore empty lines
69+
#if echo "$line" | grep -q '^\s*$'; then continue; fi
70+
if islineempty "$line"; then continue; fi
71+
# First line of new block: We need more indentation ...
72+
if test "$more" = "1"; then
73+
if ! echo "$line" | grep -q "^$previndent\s"; then return; fi
74+
more=$(echo "$line" | sed "s/^$previndent\\(\s*\\)[^\s].*\$/\\1/")
75+
#echo "$previndent$more# $LNNO: New indent level"
76+
fi
77+
# Detect less indentation than wanted, return
78+
#if ! echo "$line" | grep -q "^$previndent$more"; then return; fi
79+
if ! startswith "$previndent$more" "$line"; then return; fi
80+
# Strip comments if requested
81+
#if test -n "$RMVCOMMENT" && echo "$line" | grep -q '^\s*#'; then continue; fi
82+
if test -n "$RMVCOMMENT" && islinecomment "$line"; then continue; fi
83+
# OK, we we have at least the indentation level needed
84+
# 3 cases:
85+
# (a) We are prior to finding the right block, continue searching
86+
# (b1) Found it, no more nesting needed, output till the end
87+
# (b2) Found it, recurse into next keyword
88+
# (c) We idenitfy the end by finding a different keyword
89+
#
90+
# Case b1: No more keywords to look for, we found the previous ones if we
91+
# got here, just output until the less indentation clause above indicates the end
92+
if test -z "$1"; then
93+
#echo "$previndent$more# $LNNO: Outputing block"
94+
#if test -z "$REMOVE" || ! echo "$line" | grep -q "^$previndent$more$REMOVE:"; then
95+
if test -z "$REMOVE" || ! startswith "$previndent$more$REMOVE:" "$line"; then
96+
echo "$line"
97+
fi
98+
continue
99+
fi
100+
# b2: Search for the keyword
101+
#if echo "$line" | grep -q "^$previndent$more$1:"; then
102+
if startswith "$previndent$more$1:" "$line"; then
103+
#echo "$previndent$more# $LNNO: Found keyword $1"
104+
# Output tree unless we suppress it
105+
if test -z "$RMVTREE"; then
106+
echo "$line"
107+
else
108+
# At the leaf, we may hold a value
109+
if test -z "$2"; then
110+
echo "$line" | grep --color=never "^$previndent$more$1: [^\\s]"
111+
fi
112+
fi
113+
shift
114+
# TODO: Reformat INSERT to match
115+
if test -z "$1"; then
116+
NOTFOUND=0
117+
if test -n "$INSERT"; then echo "$INSERT"; fi
118+
fi
119+
extract_yaml_rec "$previndent$more" "1" "$@"
120+
# TODO: Reformat APPEND to match
121+
if test -z "$1" -a -n "$APPEND"; then echo "$APPEND"; fi
122+
# A return here would allows for only one block of a kind
123+
return $NOTFOUND
124+
# Otherwise we would have needed to save "$@" adn restore it here
125+
fi
126+
# a: OK, just continue to search (without the return above, this is also c)
127+
done
128+
return $NOTFOUND
129+
}
130+
131+
# Helper: extract_yaml
132+
# $1: The tag to search for and output (separated by dots)
133+
extract_yaml()
134+
{
135+
LNNO=0
136+
SRCH=($(echo "$1" | sed 's/\./ /g'))
137+
extract_yaml_rec "" "" "${SRCH[@]}"
138+
}
139+

0 commit comments

Comments
 (0)