Commit ed23b92f authored by Pierre Smeyers's avatar Pierre Smeyers
Browse files

Merge branch 'feat/envsubst-enhancements' into 'master'

feat: variables substitution enhancements

See merge request to-be-continuous/helm!100
parents 7b780c09 946f002e
Loading
Loading
Loading
Loading
+10 −11
Original line number Diff line number Diff line
@@ -156,18 +156,17 @@ and also by using available environment variables in your scripts and [values](h
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)

While your scripts may simply use any of those variables, your [values](https://helm.sh/docs/chart_best_practices/values/) files can use  **variable substitution**
with the syntax `${VARIABLE_NAME}`.
Each of those patterns will be dynamically replaced in your files by the template right before using it.
#### Variables substitution mechanism (in values files)

> :warning: 
>
> In order to be properly replaced, variables in your YAML value files shall be written with curly braces (ex: `${MYVAR}` and not `$MYVAR`).
> Multiline variables must be surrounded by double quotes and you might have to disable line-length rule of yamllint as they are rewritten on a single line.
> 
> ```yaml
>  tlsKey: "${MYKEY}"  # yamllint disable-line rule:line-length
> ```
While your scripts may freely use any of the available variables, your [values files](https://helm.sh/docs/chart_best_practices/values/) 
can use a **variable 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.

### Environments URL management

+78 −9
Original line number Diff line number Diff line
@@ -645,9 +645,78 @@ stages:
    fi
  }

  function awkenvsubst() {
    # performs variables escaping: '&' for gsub + JSON chars ('\' and '"')
    awk '{while(match($0,"[$%]{[^}]*}")) {var=substr($0,RSTART+2,RLENGTH-3);val=ENVIRON[var];gsub(/["\\&]/,"\\\\&",val);gsub("[$%]{"var"}",val)}}1'
  function tbc_envsubst() {
    awk '
      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() {
@@ -670,7 +739,7 @@ stages:
    environment_url=${ENV_URL:-$HELM_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
    # extract hostname from $environment_url
    hostname=$(echo "$environment_url" | awk -F[/:] '{print $4}')
@@ -702,13 +771,13 @@ stages:

    if [ -n "$HELM_COMMON_VALUES" ]; then
      log_info "--- using \\e[32mcommon values\\e[0m file: \\e[33;1m${HELM_COMMON_VALUES}\\e[0m"
      awkenvsubst < "$HELM_COMMON_VALUES" > generated-values-common.yml
      TBC_ENVSUBST_ENCODING=jsonstr tbc_envsubst "$HELM_COMMON_VALUES" > generated-values-common.yml
      helm_opts="$helm_opts --values generated-values-common.yml"
    fi

    if [ -n "$values_files" ]; then
      log_info "--- using \\e[32mvalues\\e[0m file: \\e[33;1m${values_files}\\e[0m"
      awkenvsubst < "$values_files" > generated-values.yml
      TBC_ENVSUBST_ENCODING=jsonstr tbc_envsubst "$values_files" > generated-values.yml
      helm_opts="$helm_opts --values generated-values.yml"
    fi

@@ -1002,7 +1071,7 @@ helm-values-lint:
      - VAR_PREFIX: PROD
  script:
    - values_file=$(eval echo "\$HELM_${VAR_PREFIX}_VALUES")
    - awkenvsubst < "$values_file" > generated-values.yml
    - TBC_ENVSUBST_ENCODING=jsonstr tbc_envsubst "$values_file" > generated-values.yml
    - yamllint -d "$HELM_YAMLLINT_CONFIG" $HELM_YAMLLINT_ARGS generated-values.yml
  rules:
    # exclude tags
@@ -1067,9 +1136,9 @@ helm-score:
      - ENV_TYPE: production
        VAR_PREFIX: PROD
  script:
    - awkenvsubst < "${HELM_COMMON_VALUES:-/dev/null}" > generated-values-common.yml
    - TBC_ENVSUBST_ENCODING=jsonstr tbc_envsubst "${HELM_COMMON_VALUES:-/dev/null}" > generated-values-common.yml
    - env_values=$(eval echo "\$HELM_${VAR_PREFIX}_VALUES")
    - awkenvsubst < "$env_values" > generated-values-env.yml
    - TBC_ENVSUBST_ENCODING=jsonstr tbc_envsubst "$env_values" > generated-values-env.yml
    - helm template $helm_package ${HELM_K8S_VERSION:+--kube-version "$HELM_K8S_VERSION"} --values generated-values-common.yml --values generated-values-env.yml | kube-score score ${HELM_K8S_VERSION:+--kubernetes-version "$HELM_K8S_VERSION"} ${HELM_KUBE_SCORE_ARGS} -
  rules:
    # exclude tags