Skip to content
Snippets Groups Projects
Commit 4023f7f8 authored by Pierre Smeyers's avatar Pierre Smeyers
Browse files

feat: variables substitution enhancements

The former variables substitution mechanism function has been improved:
- INFO log when a substitution occurs
- WARN log when a variable is not valuated
- appropriate encoding is automatically determined

BREAKING CHANGE: Now the variables substitution mechanism
implements complete YAML string encoding.
That might break projects that used to workaround the former
implementation flaws.
parent 71e8dc76
Branches
Tags
No related merge requests found
...@@ -144,7 +144,7 @@ Examples (with an application's base name `myapp`): ...@@ -144,7 +144,7 @@ Examples (with an application's base name `myapp`):
The Kubernetes template supports three techniques to deploy your code: The Kubernetes template supports three techniques to deploy your code:
1. script-based deployment, 1. script-based deployment,
2. template-based deployment using raw Kubernetes manifests (with variables substitution), 2. template-based deployment using raw Kubernetes manifests (with [variables substitution](#variables-substitution-mechanism)),
3. template-based deployment using [Kustomization files](https://kubectl.docs.kubernetes.io/references/kustomize/kustomization/). 3. template-based deployment using [Kustomization files](https://kubectl.docs.kubernetes.io/references/kustomize/kustomization/).
#### 1: script-based deployment #### 1: script-based deployment
...@@ -167,7 +167,7 @@ in your project structure, and let the GitLab CI template [`kubectl apply`](http ...@@ -167,7 +167,7 @@ in your project structure, and let the GitLab CI template [`kubectl apply`](http
The template processes the following steps: The template processes the following steps:
1. _optionally_ executes the `k8s-pre-apply.sh` script in your project to perform specific environment pre-initialization (for e.g. create required services), 1. _optionally_ executes the `k8s-pre-apply.sh` script in your project to perform specific environment pre-initialization (for e.g. create required services),
2. looks for your Kubernetes deployment file, performs [variables substitution](#using-variables) and [`kubectl apply`](https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#apply) it, 2. looks for your Kubernetes deployment file, performs [variables substitution](#variables-substitution-mechanism) and [`kubectl apply`](https://kubernetes.io/docs/reference/generated/kubectl/kubectl-commands#apply) it,
1. look for a specific `deployment-$environment_type.yml` in your project (e.g. `deployment-staging.yml` for staging environment), 1. look for a specific `deployment-$environment_type.yml` in your project (e.g. `deployment-staging.yml` for staging environment),
2. fallbacks to default `deployment.yml`. 2. fallbacks to default `deployment.yml`.
3. _optionally_ executes the `k8s-post-apply.sh` script in your project to perform specific environment post-initialization stuff, 3. _optionally_ executes the `k8s-post-apply.sh` script in your project to perform specific environment post-initialization stuff,
...@@ -202,7 +202,7 @@ After deployment (either script-based or template-based), the GitLab CI template ...@@ -202,7 +202,7 @@ After deployment (either script-based or template-based), the GitLab CI template
The Kubernetes template supports three techniques to destroy an environment (actually only review environments): The Kubernetes template supports three techniques to destroy an environment (actually only review environments):
1. script-based deployment, 1. script-based deployment,
2. template-based deployment using raw Kubernetes manifests (with variables substitution), 2. template-based deployment using raw Kubernetes manifests (with [variables substitution](#variables-substitution-mechanism)),
3. template-based deployment using [Kustomization files](https://kubectl.docs.kubernetes.io/references/kustomize/kustomization/). 3. template-based deployment using [Kustomization files](https://kubectl.docs.kubernetes.io/references/kustomize/kustomization/).
#### 1: script-based cleanup #### 1: script-based cleanup
...@@ -282,20 +282,26 @@ by using available environment variables: ...@@ -282,20 +282,26 @@ by using available environment variables:
3. any [custom variable](https://docs.gitlab.com/ee/ci/variables/#for-a-project) 3. any [custom variable](https://docs.gitlab.com/ee/ci/variables/#for-a-project)
(ex: `${SECRET_TOKEN}` that you have set in your project CI/CD variables) (ex: `${SECRET_TOKEN}` that you have set in your project CI/CD variables)
While your scripts may simply use any of those variables, your Kubernetes and Kustomize resources can use **variable substitution** #### Variables substitution mechanism
with the syntax `${VARIABLE_NAME}`.
Each of those patterns will be dynamically replaced in your resources by the template right before using it.
You can prevent any line from being processed by appending `# nosubst` at the end of the line. While your scripts may freely use any of the available variables, your Kubernetes and Kustomize
For instance in the following example, `${REMOTE_SERVICE_NAME}` won't be replaced by its environment value during GitLab job execution: resources can use a **variables substitution** mechanism implemented by the template:
- Using the syntax `${VARIABLE_NAME}` or `%{VARIABLE_NAME}`.\
:warning: Curly braces (`{}`) are mandatory in the expression (`$VARIABLE_NAME` won't be processed).
- Each of those expressions will be **dynamically expanded** in your resource files with the variable value, right before being used.
- Variable substitution expressions **must be contained in double-quoted strings**.
The substitution implementation takes care of escaping characters that need to be (double quote `"`, backslash `\`, tab `\t`, carriage return `\n` and line feed `\r`).
- Variable substitution can be prevented by appending `# nosubst` at the end of any line.\
Ex:
```yaml ```yaml
apiVersion: v1 apiVersion: v1
kind: ConfigMap kind: ConfigMap
metadata: metadata:
# ${environment_name} will be expanded
labels: labels:
app: ${APPLICATION_NAME} app: "${environment_name}"
name: ${APPLICATION_NAME} name: "${environment_name}"
data: data:
application.yml: | application.yml: |
remote: remote:
...@@ -303,21 +309,6 @@ data: ...@@ -303,21 +309,6 @@ data:
name: '${REMOTE_SERVICE_NAME}' # nosubst name: '${REMOTE_SERVICE_NAME}' # nosubst
``` ```
> :warning: In order to be properly replaced, curly braces are mandatory (ex: `${MYVAR}` and not `$MYVAR`).
> Moreover, multiline variables must be surrounded by **double quotes** (`"`).
>
> Example:
>
> ```yaml
> [...]
> containers:
> - name: restaurant-app
> env:
> # multiline variable
> - name: MENU
> value: "${APP_MENU}"
> ```
### Environments URL management ### Environments URL management
The K8S template supports two ways of providing your environments url: The K8S template supports two ways of providing your environments url:
......
...@@ -419,9 +419,78 @@ stages: ...@@ -419,9 +419,78 @@ stages:
echo "$1" | tr '[:lower:]' '[:upper:]' | tr '[:punct:]' '_' echo "$1" | tr '[:lower:]' '[:upper:]' | tr '[:punct:]' '_'
} }
function awkenvsubst() { function tbc_envsubst() {
# performs variables escaping: '&' for gsub + JSON chars ('\' and '"') awk '
awk '!/# *nosubst/{while(match($0,"[$%]{[^}]*}")) {var=substr($0,RSTART+2,RLENGTH-3);val=ENVIRON[var];gsub(/["\\&]/,"\\\\&",val);gsub("[$%]{"var"}",val)}}1' BEGIN {
count_replaced_lines = 0
# ASCII codes
for (i=0; i<=255; i++)
char2code[sprintf("%c", i)] = i
}
# determine encoding (from env or from file extension)
function encoding() {
enc = ENVIRON["TBC_ENVSUBST_ENCODING"]
if (enc != "")
return enc
if (match(FILENAME, /\.(json|yaml|yml)$/))
return "jsonstr"
return "raw"
}
# see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent
function uriencode(str) {
len = length(str)
enc = ""
for (i=1; i<=len; i++) {
c = substr(str, i, 1);
if (index("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.!~*'\''()", c))
enc = enc c
else
enc = enc "%" sprintf("%02X", char2code[c])
}
return enc
}
!/# *nosubst/ {
orig_line = $0
line = $0
count_repl_in_line = 0
# /!\ 3rd arg (match) not supported in BusyBox awk
while (match(line, /[$%]\{([[:alnum:]_]+)\}/)) {
expr_start = RSTART
expr_len = RLENGTH
# get var name
var = substr(line, expr_start+2, expr_len-3)
# get var value (from env)
val = ENVIRON[var]
# check variable is set
if (val == "") {
printf("[\033[1;93mWARN\033[0m] Environment variable \033[33;1m%s\033[0m is not set or empty\n", var) > "/dev/stderr"
} else {
enc = encoding()
if (enc == "jsonstr") {
gsub(/["\\]/, "\\\\&", val)
gsub("\n", "\\n", val)
gsub("\r", "\\r", val)
gsub("\t", "\\t", val)
} else if (enc == "uricomp") {
val = uriencode(val)
} else if (enc == "raw") {
} else {
printf("[\033[1;93mWARN\033[0m] Unsupported encoding \033[33;1m%s\033[0m: ignored\n", enc) > "/dev/stderr"
}
}
# replace expression in line
line = substr(line, 1, expr_start - 1) val substr(line, expr_start + expr_len)
count_repl_in_line++
}
if (count_repl_in_line) {
if (count_replaced_lines == 0)
printf("[\033[1;94mINFO\033[0m] Variable expansion occurred in file \033[33;1m%s\033[0m:\n", FILENAME) > "/dev/stderr"
count_replaced_lines++
printf("> line %s: %s\n", NR, orig_line) > "/dev/stderr"
}
print line
}
' "$@"
} }
function exec_hook() { function exec_hook() {
...@@ -539,8 +608,8 @@ stages: ...@@ -539,8 +608,8 @@ stages:
exit 1 exit 1
fi fi
# replace variables (alternative for envsubst which is not present in image) # variables substitution
awkenvsubst < "$deploymentfile" > generated-deployment.yml tbc_envsubst "$deploymentfile" > generated-deployment.yml
log_info "--- \\e[32mkubectl $action\\e[0m" log_info "--- \\e[32mkubectl $action\\e[0m"
kubectl ${TRACE+-v=5} "$action" -f ./generated-deployment.yml kubectl ${TRACE+-v=5} "$action" -f ./generated-deployment.yml
...@@ -562,7 +631,7 @@ stages: ...@@ -562,7 +631,7 @@ stages:
export appname_ssc=$environment_name_ssc export appname_ssc=$environment_name_ssc
# variables expansion in $environment_url # variables expansion in $environment_url
environment_url=$(echo "$environment_url" | awkenvsubst) environment_url=$(echo "$environment_url" | TBC_ENVSUBST_ENCODING=uricomp tbc_envsubst)
export environment_url export environment_url
# extract hostname from $environment_url # extract hostname from $environment_url
hostname=$(echo "$environment_url" | awk -F[/:] '{print $4}') hostname=$(echo "$environment_url" | awk -F[/:] '{print $4}')
...@@ -644,7 +713,7 @@ stages: ...@@ -644,7 +713,7 @@ stages:
export appname_ssc=$environment_name_ssc export appname_ssc=$environment_name_ssc
# variables expansion in $environment_url # variables expansion in $environment_url
environment_url=$(echo "$environment_url" | awkenvsubst) environment_url=$(echo "$environment_url" | TBC_ENVSUBST_ENCODING=uricomp tbc_envsubst)
export environment_url export environment_url
# extract hostname from $environment_url # extract hostname from $environment_url
hostname=$(echo "$environment_url" | awk -F[/:] '{print $4}') hostname=$(echo "$environment_url" | awk -F[/:] '{print $4}')
...@@ -755,8 +824,8 @@ stages: ...@@ -755,8 +824,8 @@ stages:
exit 1 exit 1
fi fi
# replace variables (alternative for envsubst which is not present in image) # variables substitution
awkenvsubst < "$deploymentfile" > generated-deployment.yml tbc_envsubst "$deploymentfile" > generated-deployment.yml
# shellcheck disable=SC2086 # shellcheck disable=SC2086
/usr/bin/kube-score score $K8S_SCORE_EXTRA_OPTS generated-deployment.yml /usr/bin/kube-score score $K8S_SCORE_EXTRA_OPTS generated-deployment.yml
...@@ -764,7 +833,7 @@ stages: ...@@ -764,7 +833,7 @@ stages:
} }
# export tool functions (might be used in after_script) # export tool functions (might be used in after_script)
export -f log_info log_warn log_error assert_defined rollback awkenvsubst export -f log_info log_warn log_error assert_defined rollback tbc_envsubst
unscope_variables unscope_variables
eval_all_secrets eval_all_secrets
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment