| garciadeblas | 70461c5 | 2024-07-03 09:17:56 +0200 | [diff] [blame] | 1 | #!/bin/bash |
| 2 | # |
| 3 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | # you may not use this file except in compliance with the License. |
| 5 | # You may obtain a copy of the License at |
| 6 | # |
| 7 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | # |
| 9 | # Unless required by applicable law or agreed to in writing, software |
| 10 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | # See the License for the specific language governing permissions and |
| 13 | # limitations under the License. |
| 14 | # |
| 15 | |
| 16 | # Convert input string to a safe name for K8s resources |
| 17 | function safe_name() { |
| 18 | local INPUT="$1" |
| 19 | |
| 20 | echo "${INPUT,,}" | \ |
| 21 | sed '/\.\// s|./||' | \ |
| 22 | sed 's|\.|-|g' | \ |
| 23 | sed 's|/|-|g' | \ |
| 24 | sed 's|_|-|g' | \ |
| 25 | sed 's| |-|g' |
| 26 | } |
| 27 | |
| 28 | |
| 29 | # Helper function to create a new age key pair |
| 30 | function create_age_keypair() { |
| 31 | local AGE_KEY_NAME="$1" |
| 32 | local CREDENTIALS_DIR="${2:-"${CREDENTIALS_DIR}"}" |
| 33 | |
| 34 | # Delete the keys in case they existed already |
| 35 | rm -f "${CREDENTIALS_DIR}/${AGE_KEY_NAME}.key" "${CREDENTIALS_DIR}/${AGE_KEY_NAME}.pub" |
| 36 | |
| 37 | # Private key |
| 38 | age-keygen -o "${CREDENTIALS_DIR}/${AGE_KEY_NAME}.key" |
| 39 | |
| 40 | # Public key (extracted from comment at private key) |
| 41 | age-keygen -y "${CREDENTIALS_DIR}/${AGE_KEY_NAME}.key" > "${CREDENTIALS_DIR}/${AGE_KEY_NAME}.pub" |
| 42 | } |
| 43 | |
| 44 | |
| 45 | # Helper function to in-place encrypt secrets in manifest |
| 46 | function encrypt_secret_inplace() { |
| 47 | local FILE="$1" |
| 48 | local PUBLIC_KEY="$2" |
| 49 | |
| 50 | sops \ |
| 51 | --age=${PUBLIC_KEY} \ |
| 52 | --encrypt \ |
| 53 | --encrypted-regex '^(data|stringData)$' \ |
| 54 | --in-place "${FILE}" |
| 55 | } |
| 56 | |
| 57 | |
| 58 | # Helper function to encrypt secrets from stdin |
| 59 | function encrypt_secret_from_stdin() { |
| 60 | local PUBLIC_KEY="$1" |
| 61 | |
| 62 | # Save secret manifest to temporary file |
| 63 | local TMPFILE=$(mktemp /tmp/secret.XXXXXXXXXX) || exit 1 |
| 64 | cat > "${TMPFILE}" |
| 65 | # NOTE: Required workaround for busybox's version of `mktemp`, which is quite limited and does not support temporary files with extensions. |
| 66 | # `.yaml` is required for proper `sops` behaviour. |
| 67 | mv "${TMPFILE}" "${TMPFILE}.yaml" |
| 68 | |
| 69 | # Encrypt |
| 70 | sops \ |
| 71 | --age=${PUBLIC_KEY} \ |
| 72 | --encrypt \ |
| 73 | --encrypted-regex '^(data|stringData)$' \ |
| 74 | --in-place "${TMPFILE}.yaml" |
| 75 | |
| 76 | # Outputs the result and removes the temporary file |
| 77 | cat "${TMPFILE}.yaml" && rm -f "${TMPFILE}.yaml" |
| 78 | } |
| 79 | |
| 80 | |
| 81 | # Helper function to create secret manifest and encrypt with public key |
| 82 | function kubectl_encrypt() { |
| 83 | local PUBLIC_KEY="$1" |
| 84 | |
| 85 | # Gathers all optional parameters for transformer funcion (if any) and puts them into an array for further use |
| 86 | local ALL_PARAMS=( "${@}" ) |
| 87 | local PARAMS=( "${ALL_PARAMS[@]:1}" ) |
| 88 | |
| 89 | kubectl \ |
| 90 | "${PARAMS[@]}" | \ |
| 91 | encrypt_secret_from_stdin \ |
| 92 | "${PUBLIC_KEY}" |
| 93 | } |
| 94 | |
| 95 | |
| 96 | # Generator function to convert source folder to `ResourceList` |
| 97 | function folder2list_generator() { |
| 98 | local FOLDER="${1:-}" |
| 99 | local SUBSTENV="${2:-"false"}" |
| 100 | local FILTER="${3:-""}" |
| 101 | |
| 102 | if [[ "${SUBSTENV,,}" == "true" ]]; |
| 103 | then |
| 104 | # Mix input with new generated manifests and replace environment variables |
| 105 | join_lists \ |
| 106 | <(cat) \ |
| 107 | <( |
| 108 | kpt fn source "${FOLDER}" | \ |
| 109 | replace_env_vars "${FILTER}" |
| 110 | ) |
| 111 | else |
| 112 | # Mix input with new generated manifests |
| 113 | join_lists \ |
| 114 | <(cat) \ |
| 115 | <( |
| 116 | kpt fn source "${FOLDER}" |
| 117 | ) |
| 118 | fi |
| 119 | |
| 120 | } |
| 121 | |
| 122 | |
| 123 | # Function to convert source folder to `ResourceList` (no generator) |
| 124 | function folder2list() { |
| 125 | local FOLDER="${1:-}" |
| 126 | |
| 127 | kpt fn source "${FOLDER}" |
| 128 | } |
| 129 | |
| 130 | |
| 131 | # Helper function to convert manifest to `ResourceList` |
| 132 | function manifest2list() { |
| 133 | kustomize cfg cat --wrap-kind ResourceList |
| 134 | } |
| 135 | |
| 136 | |
| 137 | # Helper function to convert `ResourceList` to manifests in folder structure. |
| 138 | # - New folder must be created to render the manifests. |
| 139 | function list2folder() { |
| 140 | local FOLDER="${1:-}" |
| 141 | local DRY_RUN="${2:-${DRY_RUN:-false}}" |
| 142 | |
| 143 | if [[ "${DRY_RUN,,}" == "true" ]]; |
| 144 | then |
| 145 | cat |
| 146 | else |
| 147 | kpt fn sink "${FOLDER}" |
| 148 | fi |
| 149 | } |
| 150 | |
| 151 | |
| 152 | # Helper function to convert `ResourceList` to manifests in folder structure. |
| 153 | # - It copies (cp) the generated files/subfolders over the target folder. |
| 154 | # - Pre-existing files and subfolder structure in target folder is preserved. |
| 155 | function list2folder_cp_over() { |
| 156 | local FOLDER="${1:-}" |
| 157 | local DRY_RUN="${2:-${DRY_RUN:-false}}" |
| 158 | |
| 159 | if [[ "${DRY_RUN,,}" == "true" ]]; |
| 160 | then |
| 161 | cat |
| 162 | else |
| 163 | local TMPFOLDER=$(mktemp -d) || exit 1 |
| 164 | kpt fn sink "${TMPFOLDER}/manifests" |
| 165 | |
| 166 | # Copy the generated files over the target folder |
| 167 | mkdir -p "${FOLDER}/" |
| 168 | cp -r "${TMPFOLDER}/manifests/"* "${FOLDER}/" |
| 169 | |
| 170 | # Delete temporary folder |
| 171 | rm -rf "${TMPFOLDER}" |
| 172 | fi |
| 173 | } |
| 174 | |
| 175 | |
| 176 | # Helper function to convert `ResourceList` to manifests in folder structure. |
| 177 | # - It syncs the generated files/subfolders over the target folder. |
| 178 | # - Pre-existing files and subfolder structure in target folder is deleted if not present in `ResourceList`. |
| 179 | function list2folder_sync_replace() { |
| 180 | local FOLDER="${1:-}" |
| 181 | local DRY_RUN="${2:-${DRY_RUN:-false}}" |
| 182 | |
| 183 | if [[ "${DRY_RUN,,}" == "true" ]]; |
| 184 | then |
| 185 | cat |
| 186 | else |
| 187 | local TMPFOLDER=$(mktemp -d) || exit 1 |
| 188 | kpt fn sink "${TMPFOLDER}/manifests" |
| 189 | |
| 190 | # Copy the generated files over the target folder |
| 191 | mkdir -p "${FOLDER}/" |
| 192 | rsync -arh --exclude ".git" --exclude ".*" --delete \ |
| 193 | "${TMPFOLDER}/manifests/" "${FOLDER}/" |
| 194 | |
| 195 | # Delete temporary folder |
| 196 | rm -rf "${TMPFOLDER}" |
| 197 | fi |
| 198 | } |
| 199 | |
| 200 | |
| 201 | # Helper function to render **SAFELY** a single manifest coming from stdin into a profile, with a proper KSU subfolder |
| 202 | function render_manifest_over_ksu() { |
| 203 | local KSU_NAME="$1" |
| 204 | local TARGET_PROFILE_FOLDER="$2" |
| 205 | local MANIFEST_FILENAME="$3" |
| 206 | |
| 207 | manifest2list | \ |
| 208 | set_filename_to_items \ |
| 209 | "${MANIFEST_FILENAME}" | \ |
| 210 | prepend_folder_path \ |
| 211 | "${KSU_NAME}/" | \ |
| 212 | list2folder_cp_over \ |
| 213 | "${TARGET_PROFILE_FOLDER}" |
| 214 | } |
| 215 | |
| 216 | |
| 217 | # Set filename to `ResourceList` item |
| 218 | function set_filename_to_items() { |
| 219 | local FILENAME="$1" |
| 220 | |
| 221 | yq "(.items[]).metadata.annotations.\"config.kubernetes.io/path\" |= \"${FILENAME}\"" | \ |
| 222 | yq "(.items[]).metadata.annotations.\"internal.config.kubernetes.io/path\" |= \"${FILENAME}\"" |
| 223 | } |
| 224 | |
| 225 | |
| 226 | # Prepend folder path to `ResourceList` |
| 227 | function prepend_folder_path() { |
| 228 | local PREFIX="$1" |
| 229 | |
| 230 | if [[ (-z "${PREFIX}") || ("${PREFIX}" == ".") ]]; |
| 231 | then |
| 232 | cat |
| 233 | else |
| 234 | yq "(.items[]).metadata.annotations.\"config.kubernetes.io/path\" |= \"${PREFIX}\" + ." | \ |
| 235 | yq "(.items[]).metadata.annotations.\"internal.config.kubernetes.io/path\" |= \"${PREFIX}\" + ." |
| 236 | fi |
| 237 | } |
| 238 | |
| 239 | |
| 240 | # Rename file in `ResourceList` |
| 241 | function rename_file_in_items() { |
| 242 | local SOURCE_NAME="$1" |
| 243 | local DEST_NAME="$2" |
| 244 | |
| 245 | yq "(.items[].metadata.annotations | select (.\"config.kubernetes.io/path\" == \"${SOURCE_NAME}\")).\"config.kubernetes.io/path\" = \"${DEST_NAME}\"" | \ |
| 246 | yq "(.items[].metadata.annotations | select (.\"internal.config.kubernetes.io/path\" == \"${SOURCE_NAME}\")).\"internal.config.kubernetes.io/path\" = \"${DEST_NAME}\"" |
| 247 | } |
| 248 | |
| 249 | |
| 250 | # Get value from key in object in `ResourceList` |
| 251 | function get_value_from_resourcelist() { |
| 252 | local KEY_PATH="$1" |
| 253 | local TARGET_FILTERS="${2:-}" |
| 254 | # Example: To get a specific kind ("ProviderConfig") with a specific name ("default"). (TIP: Note the escaped double quotes). |
| 255 | # TARGET_FILTERS="| select(.kind == \"ProviderConfig\") | select(.metadata.name == \"default\")" |
| 256 | |
| 257 | yq "(.items[]${TARGET_FILTERS})${KEY_PATH}" |
| 258 | } |
| 259 | |
| 260 | |
| 261 | # Patch "replace" to item in `ResourceList` |
| 262 | function patch_replace() { |
| 263 | local KEY_PATH="$1" |
| 264 | local VALUE="$2" |
| 265 | local TARGET_FILTERS="${3:-}" |
| 266 | # Example: To only patch a specific kind ("ProviderConfig") with a specific name ("default"). (TIP: Note the escaped double quotes). |
| 267 | # TARGET_FILTERS="| select(.kind == \"ProviderConfig\") | select(.metadata.name == \"default\")" |
| 268 | |
| 269 | yq "(.items[]${TARGET_FILTERS})${KEY_PATH} = \"${VALUE}\"" |
| 270 | } |
| 271 | |
| 272 | |
| 273 | # Add label to item in `ResourceList` |
| 274 | function set_label() { |
| 275 | local KEY="$1" |
| 276 | local VALUE="$2" |
| 277 | local TARGET_FILTERS="${3:-}" |
| 278 | # Example: To only patch a specific kind ("ProviderConfig") with a specific name ("default"). (TIP: Note the escaped double quotes). |
| 279 | # TARGET_FILTERS="| select(.kind == \"ProviderConfig\") | select(.metadata.name == \"default\")" |
| 280 | |
| 281 | yq "(.items[]${TARGET_FILTERS}).metadata.labels.${KEY} = \"${VALUE}\"" |
| 282 | } |
| 283 | |
| 284 | |
| 285 | # Patch which "appends" to list existing in item in `ResourceList` |
| 286 | function patch_add_to_list() { |
| 287 | local KEY_PATH="$1" |
| 288 | local VALUE="$2" |
| 289 | local TARGET_FILTERS="${3:-}" |
| 290 | # Example: To only patch a specific kind ("ProviderConfig") with a specific name ("default"). (TIP: Note the escaped double quotes). |
| 291 | # TARGET_FILTERS="| select(.kind == \"ProviderConfig\") | select(.metadata.name == \"default\")" |
| 292 | |
| 293 | local VALUE_AS_JSON="$(echo "${VALUE}" | yq -o json -I0)" |
| 294 | |
| 295 | yq "(.items[]${TARGET_FILTERS})${KEY_PATH} += ${VALUE_AS_JSON}" |
| 296 | } |
| 297 | |
| 298 | |
| 299 | # Patch which removes from list, existing in item in `ResourceList` |
| 300 | function patch_delete_from_list() { |
| 301 | local KEY_PATH="$1" |
| 302 | local TARGET_FILTERS="${2:-}" |
| 303 | |
| 304 | # local VALUE_AS_JSON="$(echo "${VALUE}" | yq -o json -I0)" |
| 305 | |
| 306 | yq "del((.items[]${TARGET_FILTERS})${KEY_PATH})" |
| 307 | } |
| 308 | |
| 309 | |
| 310 | # Check if an element/value is in a given list, existing in item in `ResourceList` |
| 311 | function is_element_on_list() { |
| 312 | local KEY_PATH="$1" |
| 313 | local VALUE="$2" |
| 314 | local TARGET_FILTERS="${3:-}" |
| 315 | # Example: To only patch a specific kind ("ProviderConfig") with a specific name ("default"). (TIP: Note the escaped double quotes). |
| 316 | # TARGET_FILTERS="| select(.kind == \"ProviderConfig\") | select(.metadata.name == \"default\")" |
| 317 | |
| 318 | TEST_RESULT=$( |
| 319 | cat | \ |
| 320 | yq "(.items[]${TARGET_FILTERS})${KEY_PATH} == \"${VALUE}\"" | grep "true" |
| 321 | ) |
| 322 | |
| 323 | if [[ "${TEST_RESULT}" != "true" ]] |
| 324 | then |
| 325 | echo "false" |
| 326 | else |
| 327 | echo "true" |
| 328 | fi |
| 329 | } |
| 330 | |
| 331 | |
| 332 | # Patch "replace" to item in `ResourceList` using a JSON as value |
| 333 | function patch_replace_inline_json() { |
| 334 | local KEY_PATH="$1" |
| 335 | local VALUE="$2" |
| 336 | local TARGET_FILTERS="${3:-}" |
| 337 | # Example: To only patch a specific kind ("ProviderConfig") with a specific name ("default"). (TIP: Note the escaped double quotes). |
| 338 | # TARGET_FILTERS="| select(.kind == \"ProviderConfig\") | select(.metadata.name == \"default\")" |
| 339 | |
| 340 | VALUE_AS_JSON="$(echo "${VALUE}" | yq -o=json)" yq "(.items[]${TARGET_FILTERS})${KEY_PATH} = strenv(VALUE_AS_JSON)" |
| 341 | } |
| 342 | |
| 343 | |
| 344 | # Delete full object from `ResourceList` |
| 345 | function delete_object() { |
| 346 | local OBJECT_NAME="$1" |
| 347 | local KIND_NAME="$2" |
| 348 | local API_VERSION="${3:-""}" |
| 349 | |
| 350 | # Calculated inputs |
| 351 | if [[ -z "${API_VERSION}" ]] |
| 352 | then |
| 353 | # If `apiVersion` is not specified |
| 354 | local TARGET_FILTER="| select(.kind == \"${KIND_NAME}\") | select(.metadata.name == \"${OBJECT_NAME}\")" |
| 355 | else |
| 356 | # Otherwise, it is taken into account |
| 357 | local TARGET_FILTER="| select(.kind == \"${KIND_NAME}\") | select(.apiVersion == \"${API_VERSION}\") | select(.metadata.name == \"${OBJECT_NAME}\")" |
| 358 | fi |
| 359 | |
| 360 | # Delete object |
| 361 | yq "del((.items[]${TARGET_FILTER}))" |
| 362 | } |
| 363 | |
| 364 | |
| 365 | # Empty transformer function |
| 366 | function noop_transformer() { |
| 367 | cat |
| 368 | } |
| 369 | |
| 370 | |
| 371 | # Add patch to `Kustomization` item in `ResourceList` |
| 372 | function add_patch_to_kustomization() { |
| 373 | local KUSTOMIZATION_NAME="$1" |
| 374 | local FULL_PATCH_CONTENT="$2" |
| 375 | |
| 376 | patch_add_to_list \ |
| 377 | ".spec.patches" \ |
| 378 | "${FULL_PATCH_CONTENT}" \ |
| 379 | "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${KUSTOMIZATION_NAME}\")" |
| 380 | } |
| 381 | |
| rshri | 4240a7d | 2025-06-13 11:30:35 +0000 | [diff] [blame] | 382 | function patch_add_value_as_list() { |
| 383 | local KEY_PATH="$1" |
| 384 | local VALUE="$2" |
| 385 | local TARGET_FILTERS="${3:-}" |
| 386 | |
| 387 | yq "(.items[]${TARGET_FILTERS})${KEY_PATH} += [${VALUE}]" |
| 388 | } |
| 389 | |
| 390 | function add_patch_to_kustomization_as_list() { |
| 391 | local KUSTOMIZATION_NAME="$1" |
| 392 | local PATCH_VALUE="$2" |
| 393 | |
| 394 | local VALUE_AS_JSON=$(echo "$PATCH_VALUE" | yq -o json -I0) |
| 395 | |
| 396 | patch_add_value_as_list \ |
| 397 | ".spec.patches" \ |
| 398 | "${VALUE_AS_JSON}" \ |
| 399 | "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${KUSTOMIZATION_NAME}\")" |
| 400 | } |
| 401 | |
| 402 | function add_component_to_kustomization_as_list() { |
| 403 | local KUSTOMIZATION_NAME="$1" |
| 404 | shift |
| 405 | local COMPONENT=("$@") |
| 406 | |
| 407 | local COMPONENT_JSON=$(printf '"%s",' "${COMPONENT[@]}" | sed 's/,$//') |
| 408 | |
| 409 | patch_add_value_as_list \ |
| 410 | ".spec.components" \ |
| 411 | "${COMPONENT_JSON}" \ |
| 412 | "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${KUSTOMIZATION_NAME}\")" |
| 413 | } |
| 414 | |
| 415 | function add_config_to_kustomization() { |
| 416 | local KUSTOMIZATION_NAME="$1" |
| 417 | |
| 418 | yq ' |
| 419 | (.items[] | select(.kind == "Kustomization") | select(.metadata.name == "'"${KUSTOMIZATION_NAME}"'")) |
| 420 | .spec.postBuild.substituteFrom = [{"kind": "ConfigMap", "name": "'"${KUSTOMIZATION_NAME}"'-parameters"}] |
| 421 | ' |
| 422 | } |
| garciadeblas | 70461c5 | 2024-07-03 09:17:56 +0200 | [diff] [blame] | 423 | |
| 424 | # Helper function to produce a JSON Patch as specified in RFC 6902 |
| 425 | function as_json_patch() { |
| 426 | local OPERATION="$1" |
| 427 | local PATCH_PATH="$2" |
| 428 | local VALUES="$3" |
| 429 | |
| 430 | # Convert to JSON dictionary to insert as map instead of string |
| 431 | local VALUES_AS_DICT=$(echo "${VALUES}" | yq -o=json) |
| 432 | |
| 433 | # Generate a patch list |
| 434 | cat <<EOF | yq ".[0].value = ${VALUES_AS_DICT}" |
| 435 | - op: ${OPERATION} |
| 436 | path: ${PATCH_PATH} |
| 437 | EOF |
| 438 | } |
| 439 | |
| 440 | |
| 441 | # Helper function to produce a full patch, with target object + JSON Patch RFC 6902 |
| 442 | function full_json_patch() { |
| 443 | local TARGET_KIND="$1" |
| 444 | local TARGET_NAME="$2" |
| 445 | local OPERATION="$3" |
| 446 | local PATCH_PATH="$4" |
| garciadeblas | c4afd54 | 2025-06-18 17:37:59 +0200 | [diff] [blame] | 447 | # Gathers all optional parameters for transformer function (if any) and puts them into an array for further use |
| garciadeblas | 70461c5 | 2024-07-03 09:17:56 +0200 | [diff] [blame] | 448 | local ALL_PARAMS=( "${@}" ) |
| 449 | local VALUES=( "${ALL_PARAMS[@]:4}" ) |
| 450 | |
| 451 | # Accumulates value items into the patch |
| 452 | local PATCH_CONTENT="" |
| 453 | for VAL in "${VALUES[@]}" |
| 454 | do |
| 455 | local VAL_AS_DICT=$(echo "${VAL}" | yq -o=json) |
| 456 | |
| 457 | ITEM=$( |
| 458 | yq --null-input ".op = \"${OPERATION}\", .path = \"${PATCH_PATH}\"" | \ |
| 459 | yq ".value = ${VAL_AS_DICT}" | \ |
| 460 | yq "[ . ]" |
| 461 | ) |
| 462 | |
| 463 | PATCH_CONTENT="$(echo -e "${PATCH_CONTENT}\n${ITEM}")" |
| 464 | done |
| 465 | |
| 466 | # Wrap a full patch around, adding target specification |
| 467 | local PATCH_FULL=$( |
| 468 | yq --null-input ".target.kind = \"${TARGET_KIND}\", .target.name = \"${TARGET_NAME}\"" | \ |
| garciadeblas | 6245324 | 2025-07-18 18:12:40 +0200 | [diff] [blame] | 469 | yq eval-all 'select(fileIndex == 0) * select(fileIndex == 1)' - \ |
| 470 | <(printf "patch: |-\n%s\n" "$(echo "${PATCH_CONTENT}" | sed 's/^/ /')" ) | \ |
| 471 | yq "[.]" |
| garciadeblas | 70461c5 | 2024-07-03 09:17:56 +0200 | [diff] [blame] | 472 | ) |
| 473 | |
| 474 | echo "${PATCH_FULL}" |
| 475 | } |
| 476 | |
| 477 | |
| 478 | # Add values to `HelmRelease` by patch into `Kustomization` item in `ResourceList` |
| 479 | function add_values_to_helmrelease_via_ks() { |
| 480 | local KUSTOMIZATION_NAME="$1" |
| 481 | local HELMRELEASE_NAME="$2" |
| 482 | local VALUES="$3" |
| 483 | |
| 484 | # Embed into patch list |
| 485 | local FULL_PATCH_CONTENT="$( |
| 486 | full_json_patch \ |
| 487 | "HelmRelease" \ |
| 488 | "${HELMRELEASE_NAME}" \ |
| 489 | "add" \ |
| 490 | "/spec/values" \ |
| 491 | "${VALUES}" |
| 492 | )" |
| 493 | |
| 494 | # Path via intermediate Kustomization object |
| 495 | add_patch_to_kustomization \ |
| 496 | "${KUSTOMIZATION_NAME}" \ |
| 497 | "${FULL_PATCH_CONTENT}" |
| 498 | } |
| 499 | |
| 500 | |
| 501 | # Add values from Secret/ConfigMap to `HelmRelease` by patch into `Kustomization` item in `ResourceList` |
| 502 | function add_referenced_values_to_helmrelease_via_ks() { |
| 503 | local KUSTOMIZATION_NAME="$1" |
| 504 | local HELMRELEASE_NAME="$2" |
| 505 | local VALUES_FROM="$3" |
| 506 | |
| 507 | # Embed into patch list |
| 508 | local FULL_PATCH_CONTENT="$( |
| 509 | full_json_patch \ |
| 510 | "HelmRelease" \ |
| 511 | "${HELMRELEASE_NAME}" \ |
| 512 | "add" \ |
| 513 | "/spec/valuesFrom" \ |
| 514 | "${VALUES_FROM}" |
| 515 | )" |
| 516 | |
| 517 | # Path via intermediate Kustomization object |
| 518 | add_patch_to_kustomization \ |
| 519 | "${KUSTOMIZATION_NAME}" \ |
| 520 | "${FULL_PATCH_CONTENT}" |
| 521 | } |
| 522 | |
| 523 | |
| 524 | # High level function to add values from Secret, ConfigMap or both to `HelmRelease` by patch into `Kustomization` item in `ResourceList` |
| 525 | function add_ref_values_to_hr_via_ks() { |
| 526 | local KUSTOMIZATION_NAME="$1" |
| 527 | local HELMRELEASE_NAME="$2" |
| 528 | local VALUES_SECRET_NAME="${3:-""}" |
| 529 | local VALUES_CM_NAME="${4:-""}" |
| 530 | |
| 531 | local YAML_VALUES_FROM_BOTH=$(cat <<EOF |
| 532 | - kind: Secret |
| 533 | name: "${VALUES_SECRET_NAME}" |
| 534 | - kind: ConfigMap |
| 535 | name: "${VALUES_CM_NAME}" |
| 536 | EOF |
| 537 | ) |
| 538 | local YAML_VALUES_FROM_SECRET=$(cat <<EOF |
| 539 | - kind: Secret |
| 540 | name: "${VALUES_SECRET_NAME}" |
| 541 | EOF |
| 542 | ) |
| 543 | local YAML_VALUES_FROM_CM=$(cat <<EOF |
| 544 | - kind: ConfigMap |
| 545 | name: "${VALUES_CM_NAME}" |
| 546 | EOF |
| 547 | ) |
| 548 | |
| 549 | # Chooses the appropriate YAML |
| 550 | VALUES_FROM="" |
| 551 | if [[ ( -n "${VALUES_SECRET_NAME}" ) && ( -n "${VALUES_CM_NAME}" ) ]]; |
| 552 | then |
| 553 | VALUES_FROM="${YAML_VALUES_FROM_BOTH}" |
| 554 | elif [[ -n "${VALUES_SECRET_NAME}" ]]; |
| 555 | then |
| 556 | VALUES_FROM="${YAML_VALUES_FROM_SECRET}" |
| 557 | elif [[ -n "${VALUES_CM_NAME}" ]]; |
| 558 | then |
| 559 | VALUES_FROM="${YAML_VALUES_FROM_CM}" |
| 560 | else |
| 561 | # If none is set, it must be an error |
| 562 | return 1 |
| 563 | fi |
| 564 | |
| 565 | # Calls the low-level function |
| 566 | add_referenced_values_to_helmrelease_via_ks \ |
| 567 | "${KUSTOMIZATION_NAME}" \ |
| 568 | "${HELMRELEASE_NAME}" \ |
| 569 | "${VALUES_FROM}" |
| 570 | } |
| 571 | |
| 572 | # Substitute environment variables from stdin |
| 573 | function replace_env_vars() { |
| 574 | # Optional parameter to filter environment variables that can be replaced |
| 575 | local FILTER=${1:-} |
| 576 | |
| 577 | if [[ -n "${FILTER}" ]]; |
| 578 | then |
| 579 | envsubst "${FILTER}" |
| 580 | else |
| 581 | envsubst |
| 582 | fi |
| 583 | } |
| 584 | |
| 585 | |
| 586 | # Join two `ResourceList` **files** |
| 587 | # |
| 588 | # Examples of use: |
| 589 | # $ join_lists list_file1.yaml list_file2.yaml |
| 590 | # $ join_lists <(manifest2list < manifest_file1.yaml) <(manifest2list < manifest_file2.yaml) |
| 591 | # $ cat prueba1.yaml | manifest2list | join_lists - <(manifest2list < prueba2.yaml) |
| 592 | # |
| 593 | # NOTE: Duplicated keys and arrays may be overwritten by the latest file. |
| 594 | # See: https://stackoverflow.com/questions/66694238/merging-two-yaml-documents-while-concatenating-arrays |
| 595 | function join_lists() { |
| 596 | local FILE1="$1" |
| 597 | local FILE2="$2" |
| 598 | |
| 599 | yq eval-all '. as $item ireduce ({}; . *+ $item)' \ |
| 600 | "${FILE1}" \ |
| 601 | "${FILE2}" |
| 602 | } |
| 603 | |
| 604 | |
| 605 | # Helper function to create a generator from a function that creates manifests |
| 606 | function make_generator() { |
| 607 | local MANIFEST_FILENAME="$1" |
| 608 | local SOURCER_FUNCTION="$2" |
| 609 | # Gathers all optional parameters for the funcion (if any) and puts them into an array for further use |
| 610 | local ALL_PARAMS=( "${@}" ) |
| 611 | local PARAMS=( "${ALL_PARAMS[@]:2}" ) |
| 612 | |
| 613 | # Mix input with new generated manifests |
| 614 | join_lists \ |
| 615 | <(cat) \ |
| 616 | <( |
| 617 | "${SOURCER_FUNCTION}" \ |
| 618 | "${PARAMS[@]}" | \ |
| 619 | manifest2list | \ |
| 620 | set_filename_to_items "${MANIFEST_FILENAME}" |
| 621 | ) |
| 622 | } |
| 623 | |
| 624 | |
| 625 | function transform_if() { |
| 626 | local TEST_RESULT=$1 |
| 627 | |
| 628 | # Gathers all optional parameters for transformer funcion (if any) and puts them into an array for further use |
| 629 | local ALL_PARAMS=( "${@}" ) |
| 630 | local PARAMS=( "${ALL_PARAMS[@]:1}" ) |
| 631 | |
| 632 | # If test result is true (==0), then runs the transformation normally |
| 633 | if [[ "${TEST_RESULT}" == "0" ]]; |
| 634 | then |
| 635 | "${PARAMS[@]}" |
| 636 | # Otherwise, just pass through |
| 637 | else |
| 638 | cat |
| 639 | fi |
| 640 | } |
| 641 | |
| 642 | |
| 643 | # Helper function to convert multiline input from stdin to comma-separed output |
| 644 | function multiline2commalist() { |
| 645 | mapfile -t TMP_ARRAY < <(cat) |
| 646 | printf -v TMP_LIST '%s,' "${TMP_ARRAY[@]}" |
| 647 | echo "${TMP_LIST}" | sed 's/,$//g' |
| 648 | } |
| 649 | |
| 650 | |
| 651 | # Helper function to check pending changes in workdir to `fleet` repo |
| 652 | function check_fleet_workdir_status() { |
| 653 | local FLEET_REPO_DIR="${1:-${FLEET_REPO_DIR}}" |
| 654 | |
| 655 | pushd "${FLEET_REPO_DIR}" |
| 656 | git status |
| 657 | popd |
| 658 | } |
| 659 | |
| 660 | |
| 661 | # Helper function to commit changes in workdir to `fleet` repo |
| 662 | function commit_and_push_to_fleet() { |
| 663 | local DEFAULT_COMMIT_MESSAGE="Committing latest changes to fleet repo at $(date +'%Y-%m-%d %H:%M:%S')" |
| 664 | local COMMIT_MESSAGE="${1:-${DEFAULT_COMMIT_MESSAGE}}" |
| 665 | local FLEET_REPO_DIR="${2:-${FLEET_REPO_DIR}}" |
| 666 | |
| 667 | pushd "${FLEET_REPO_DIR}" |
| 668 | git status |
| 669 | git add -A |
| 670 | git commit -m "${COMMIT_MESSAGE}" |
| 671 | echo "${COMMIT_MESSAGE}" |
| 672 | git push |
| 673 | popd |
| 674 | } |