CI/CD-Variablen in Gitlab und das Auslesen mit env
Wer kennt es nicht? Ein Docker Container benötigt im produktiven Umfeld einen bestimmten Port oder API-Keys sollen erst zur Laufzeit an den Container übergeben werden. Environment-Variablen wie z.B. .env oder .env.local dienen genau diesem Zweck. Selbstverständlich können sie auch in CI/CD-Pipelines benutzt werden. Gibt es da allerdings nicht eine wichtige Security-Empfehlung API-Keys nicht in das Repository zu schreiben? Und wäre es denn auch nicht viel schöner Ports und weitere wichtige Laufzeitvariablen unabhängig vom Repository und somit auch von Branches zu definieren?
Dafür bietet Gitlab für CI/CD-Pipelines Variablen an. Diese können auf Projekt- und Gruppenebene definiert werden. Sie sind keinem Branch zugeordnet und gelten somit global. Ich empfehle beim Erstellen von Variablen ein einheitliches Schema einzuhalten. Eine Trennung mit Binde- oder Unterstrichen hilft die Variablen in der Pipeline besser zuordnen zu können. Bewährt haben sich Präfixe, die anzeigen, ob es sich um Build- oder Laufzeitvariablen handelt. Weiterhin kann dann optional noch ein PROD oder STAGING vorangestellt werden. Eine Laufzeit-Variable, die für Staging-Umgebungen den Namen der Datenbank enthält, könnte z.B. so lautet: STAGING_RUN_DATABASE_NAME. Nach diesem Schema können nun beliebig (bis zu 30.000) Variablen angelegt werden.
Erfahrenere Leser fragen sich nun vielleicht, wie man in CI/CD-YAML-Definitionen auf solche generische Variablennamen zugreift. Die Lösung ist, dies mit dem script-Keyword zu tun. In dieser Sektion wird direkt angegeben, was gitlab-runner ausführen kann. Ein entsprechend vorbereiteter Build-Container ist an dieser Stelle beliebig leistungsfähig. Wie kommt man dort nun allerdings an die Variablen ran? Im Build Container hat man Zugriff auf alle Variablen. Demzufolge gibt ein simples env alle Variablen preis. Man muss sich also nur noch einen für den Pfad der Pipeline gültigen Filter schreiben, um das gewünschte Prefix zu erkennen, zu entfernen und die Variable dann für die Weiterbearbeitung zu speichern:
declare -A docker_compose_substitute_array
for OUTPUT in $(env | sed -n "s/^${DEFAULT_VAR_PREFIX}//p")
do
OUTPUT_PREFIX=$(echo $OUTPUT | sed -n 's/\=.*//p')
eval docker_compose_substitute_array[$OUTPUT_PREFIX]=$(echo $OUTPUT | sed -n 's/^[^=]*=//p')
done
Sieht auf den ersten Blick mega kompliziert aus, ist es aber nicht. Das gezeigte Codesnippet befindet sich im Skriptbereich einer Production-Deployment-Stage. Dort ist PROD_RUN_PREFIX als "PROD_RUN" definiert. Von allen Variablen, die von env ausgegeben werden, filtert der sed-Befehl alle Variablen raus, die nicht mit PROD_RUN_ beginnen und entfernt gleichzeitig diesen Prefix. Danach wird diese Variable mit ihrem Variablennamen als Key in ein Array geschrieben.
Das Array kann dann beispielsweise mit envsubst verwendet werden, um ein vorbereitetes Template zu verarbeiten:
printarr() {
declare -n __p="$1"; for k in "${!__p[@]}"; do printf "%s=%s\n" "$k" "${__p[$k]}" ; done ;
}
eval $(printarr docker_compose_substitute_array) envsubst < $COMPOSE_TEMPLATE > compose.yaml