Feature 11019: Workflow for cloud-native operations in OSM following Gitops model
[osm/devops.git] / docker / osm-krm-functions / scripts / library / krm-functions.rc
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 function generator_encrypted_secret_cloud_credentials() {
17   local CLOUD_CREDENTIALS_FILENAME="$1"
18   local SECRET_NAME="$2"
19   local PUBLIC_KEY="$3"
20   local SECRET_MANIFEST_FILENAME="${4:-secret-${SECRET_NAME}.yaml}"
21
22   join_lists \
23     <(cat) \
24     <(cat "${CREDENTIALS_DIR}/${CLOUD_CREDENTIALS_FILENAME}" | \
25       kubectl create secret generic ${SECRET_NAME} \
26         --namespace crossplane-system \
27         --from-file creds=/dev/stdin \
28         -o yaml --dry-run=client | \
29       encrypt_secret_from_stdin "${PUBLIC_KEY_MGMT}" | \
30       manifest2list | \
31       set_filename_to_items "${SECRET_MANIFEST_FILENAME}")
32 }
33
34
35 # Create ProviderConfig for Azure
36 function add_providerconfig_for_azure() {
37   # Inputs
38   local CLOUD_CREDENTIALS="$1"
39   local NEW_SECRET_NAME="$2"
40   local PROVIDERCONFIG_NAME="${3:-default}"
41   local PUBLIC_KEY="${4:-${PUBLIC_KEY_MGMT}}"
42   local TARGET_FOLDER="${5:-${MGMT_ADDON_CONFIG_DIR}}"
43
44   # Path to folder with base templates
45   local TEMPLATES="${SW_CATALOGS_REPO_DIR}/infra-configs/crossplane/providers/azure/templates/"
46
47   # Pipeline
48   folder2list \
49     "${TEMPLATES}" | \
50   patch_replace \
51     ".metadata.name" \
52     "${PROVIDERCONFIG_NAME}" \
53     "| select(.kind == \"ProviderConfig\") | select(.metadata.name == \"default\")" | \
54   patch_replace \
55     ".spec.credentials.secretRef.name" \
56     "${NEW_SECRET_NAME}" \
57     "| select(.kind == \"ProviderConfig\") | select(.metadata.name == \"default\")" | \
58   rename_file_in_items \
59     "crossplane-providerconfig-azure.yaml" \
60     "crossplane-providerconfig-azure-${PROVIDERCONFIG_NAME}.yaml" | \
61   generator_encrypted_secret_cloud_credentials \
62     "${CLOUD_CREDENTIALS}" \
63     "${NEW_SECRET_NAME}" \
64     "${PUBLIC_KEY}" | \
65   list2folder_cp_over \
66     "${TARGET_FOLDER}"
67 }
68
69
70 # Create ProviderConfig for GCP
71 function add_providerconfig_for_gcp() {
72   # Inputs
73   local CLOUD_CREDENTIALS="$1"
74   local NEW_SECRET_NAME="$2"
75   local GCP_PROJECT="$3"
76   local PROVIDERCONFIG_NAME="${4:-default}"
77   local PUBLIC_KEY="${5:-${PUBLIC_KEY_MGMT}}"
78   local TARGET_FOLDER="${6:-${MGMT_ADDON_CONFIG_DIR}}"
79
80   # Path to folder with base templates
81   local TEMPLATES="${SW_CATALOGS_REPO_DIR}/infra-configs/crossplane/providers/gcp/templates/"
82
83   # Pipeline
84   folder2list \
85     "${TEMPLATES}" | \
86   patch_replace \
87     ".metadata.name" \
88     "${PROVIDERCONFIG_NAME}" \
89     "| select(.kind == \"ProviderConfig\") | select(.metadata.name == \"default\")" | \
90   patch_replace \
91     ".spec.credentials.secretRef.name" \
92     "${NEW_SECRET_NAME}" \
93     "| select(.kind == \"ProviderConfig\") | select(.metadata.name == \"default\")" | \
94   patch_replace \
95     ".spec.projectID" \
96     "${GCP_PROJECT}" \
97     "| select(.kind == \"ProviderConfig\") | select(.metadata.name == \"default\")" | \
98   rename_file_in_items \
99     "crossplane-providerconfig-gcp.yaml" \
100     "crossplane-providerconfig-gcp-${PROVIDERCONFIG_NAME}.yaml" | \
101   generator_encrypted_secret_cloud_credentials \
102     "${CLOUD_CREDENTIALS}" \
103     "${NEW_SECRET_NAME}" \
104     "${PUBLIC_KEY}" | \
105   list2folder_cp_over \
106     "${TARGET_FOLDER}"
107 }
108
109
110 # TODO: Deprecated
111 # Create AKS cluster (without bootstrap)
112 function create_cluster_aks() {
113   local CLUSTER_NAME="$1"
114   local VM_SIZE="$2"
115   local NODE_COUNT="$3"
116   local CLUSTER_LOCATION="$4"
117   local RG_NAME="$5"
118   local K8S_VERSION="${6:-"'1.28'"}"
119   local PROVIDERCONFIG_NAME="${7:-default}"
120   local CLUSTER_KUSTOMIZATION_NAME="${8:$(safe_name ${CLUSTER_NAME})}"
121   local TARGET_FOLDER="${9:-${MGMT_RESOURCES_DIR}}"
122   local MANIFEST_FILENAME="${10:-"${CLUSTER_NAME}.yaml"}"
123   local TEMPLATES="${11:-"${SW_CATALOGS_REPO_DIR}/cloud-resources/aks/templates/"}"
124   local TEMPLATE_MANIFEST_FILENAME="${12:-"aks01.yaml"}"
125
126   export CLUSTER_KUSTOMIZATION_NAME
127   folder2list \
128     "${TEMPLATES}" | \
129   replace_env_vars \
130     '${CLUSTER_KUSTOMIZATION_NAME}' | \
131   patch_replace \
132     ".spec.postBuild.substitute.cluster_name" \
133     "${CLUSTER_NAME}" \
134     "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
135   patch_replace \
136     ".spec.postBuild.substitute.cluster_name" \
137     "${CLUSTER_NAME}" \
138     "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
139   patch_replace \
140     ".spec.postBuild.substitute.vm_size" \
141     "${VM_SIZE}" \
142     "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
143   patch_replace \
144     ".spec.postBuild.substitute.node_count" \
145     "${NODE_COUNT}" \
146     "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
147   patch_replace \
148     ".spec.postBuild.substitute.cluster_location" \
149     "${CLUSTER_LOCATION}" \
150     "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
151   patch_replace \
152     ".spec.postBuild.substitute.rg_name" \
153     "${RG_NAME}" \
154     "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
155   patch_replace \
156     ".spec.postBuild.substitute.k8s_version" \
157     "${K8S_VERSION}" \
158     "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
159   patch_replace \
160     ".spec.postBuild.substitute.providerconfig_name" \
161     "${PROVIDERCONFIG_NAME}" \
162     "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
163   rename_file_in_items \
164     "${TEMPLATE_MANIFEST_FILENAME}" \
165     "${MANIFEST_FILENAME}" | \
166   prepend_folder_path "${CLUSTER_KUSTOMIZATION_NAME}/" | \
167   list2folder_cp_over \
168     "${TARGET_FOLDER}"
169 }
170
171
172 # Generator to create a profile folder
173 function generator_profile_folder() {
174   local CONFIGMAP_NAME="$1"
175   local PROFILE_PATH="$2"
176   local PROFILE_TYPE="$3"
177   local REPO_URL="${4:-${FLEET_REPO_URL}}"
178   local PROFILE_LOCAL_DIR="${5:-"${PROFILE_PATH}"}"
179
180   join_lists \
181     <(cat) \
182     <(kubectl create configmap $(safe_name "${CONFIGMAP_NAME}") \
183         --namespace flux-system \
184         --from-literal=repo="${REPO_URL}" \
185         --from-literal=path="${PROFILE_PATH}" \
186         -o yaml \
187         --dry-run=client  | \
188       manifest2list | \
189       set_label \
190         "osm_profile_type" \
191         "${PROFILE_TYPE}" | \
192       set_filename_to_items "profile-configmap.yaml" | \
193       prepend_folder_path "${PROFILE_LOCAL_DIR}/")
194 }
195
196
197 # Helper function to return the relative path of a profile
198 function path_to_profile() {
199   local PROFILE_NAME="$1"
200   local PROFILE_TYPE="$2"
201   local PROJECT_NAME="${3:-"${MGMT_PROJECT_NAME}"}"
202
203   case "${PROFILE_TYPE,,}" in
204
205     "controller" | "infra-controller" | "infra-controllers" | "infra_controller" | "infra_controllers")
206       echo -n "${PROJECT_NAME}/infra-controller-profiles/${PROFILE_NAME}"
207       return 0
208       ;;
209
210     "config" | "infra-config" | "infra-configs" | "infra_config" | "infra_configs")
211       echo -n "${PROJECT_NAME}/infra-config-profiles/${PROFILE_NAME}"
212       return 0
213       ;;
214
215     "managed" | "resources" | "managed-resources" | "managed_resources")
216       echo -n "${PROJECT_NAME}/managed-resources/${PROFILE_NAME}"
217       return 0
218       ;;
219
220      "app" |"apps" | "applications" | "cnf" | "cnfs" | "nf" | "nfs")
221       echo -n "${PROJECT_NAME}/app-profiles/${PROFILE_NAME}"
222       return 0
223       ;;
224
225     *)
226       echo -n "------------ ERROR ------------"
227       return 1
228       ;;
229   esac
230 }
231
232
233 # Function to create a new profile
234 function create_profile() {
235   local PROFILE_NAME="$1"
236   local PROFILE_TYPE="$2"
237   local PROJECT_NAME="${3:-"${MGMT_PROJECT_NAME}"}"
238   local FLEET_REPO_URL="${4:-"${FLEET_REPO_URL}"}"
239   local FLEET_REPO_DIR="${5:-"${FLEET_REPO_DIR}"}"
240
241   local TARGET_PROFILE_PATH="$(
242     path_to_profile \
243       "${PROFILE_NAME}" \
244       "${PROFILE_TYPE}" \
245       "${PROJECT_NAME}" \
246   )"
247
248   # Generate profile as `ResourceList` and render to target folder.
249   echo "" | \
250   generator_profile_folder \
251     "${PROFILE_NAME}-${PROFILE_TYPE}" \
252     "${TARGET_PROFILE_PATH}" \
253     "${PROFILE_TYPE}" \
254     "${FLEET_REPO_URL}" \
255     "." | \
256   list2folder_cp_over \
257     "${FLEET_REPO_DIR}/${TARGET_PROFILE_PATH}"
258 }
259
260
261 # Function to delete a profile
262 function delete_profile() {
263   local PROFILE_NAME="$1"
264   local PROFILE_TYPE="$2"
265   local PROJECT_NAME="${3:-"${MGMT_PROJECT_NAME}"}"
266   local FLEET_REPO_DIR="${4:-"${FLEET_REPO_DIR}"}"
267
268   local TARGET_PROFILE_PATH="$(
269     path_to_profile \
270       "${PROFILE_NAME}" \
271       "${PROFILE_TYPE}" \
272       "${PROJECT_NAME}" \
273   )"
274
275   # Delete the profile folder
276   rm -rf "${FLEET_REPO_DIR}/${TARGET_PROFILE_PATH}"
277 }
278
279
280 # ----- BEGIN of Helper functions for remote cluster bootstrap -----
281
282 # Generate structure of profile folders prior to bootstrap
283 function generator_profile_folders_new_cluster() {
284   # Inputs
285   local PROFILE_NAME="$1"
286   local FLEET_REPO_URL="$2"
287   local PROJECT_NAME="${3:-"${MGMT_PROJECT_NAME}"}"
288   # Optional inputs: Paths for each profile in the Git repo
289   local INFRA_CONTROLLERS_PATH="${4:-"${PROJECT_NAME}/infra-controller-profiles/${PROFILE_NAME}"}"
290   local INFRA_CONFIGS_PATH="${5:-"${PROJECT_NAME}/infra-config-profiles/${PROFILE_NAME}"}"
291   local MANAGED_RESOURCES_PATH="${6:-"${PROJECT_NAME}/managed-resources/${PROFILE_NAME}"}"
292   local APPS_PATH="${7:-"${PROJECT_NAME}/app-profiles/${PROFILE_NAME}"}"
293
294   # Generate profiles as `ResourceList`. merging with inputs
295   join_lists \
296     <(cat) \
297     <(
298       echo "" | \
299       generator_profile_folder \
300         "${PROFILE_NAME}-profile-infra-controllers" \
301         "${INFRA_CONTROLLERS_PATH}" \
302         "infra-controllers" \
303         "${FLEET_REPO_URL}" | \
304       generator_profile_folder \
305         "${PROFILE_NAME}-profile-infra-configs" \
306         "${INFRA_CONFIGS_PATH}" \
307         "infra-configs" \
308         "${FLEET_REPO_URL}" | \
309       generator_profile_folder \
310         "${PROFILE_NAME}-profile-managed-resources" \
311         "${MANAGED_RESOURCES_PATH}" \
312         "managed-resources" \
313         "${FLEET_REPO_URL}" | \
314       generator_profile_folder \
315         "${PROFILE_NAME}-profile-apps" \
316         "${APPS_PATH}" \
317         "apps" \
318         "${FLEET_REPO_URL}"
319       )
320 }
321
322
323 # Generate base Flux Kustomizations for the new cluster prior to bootstrap
324 function generator_base_kustomizations_new_cluster() {
325   local CLUSTER_KUSTOMIZATION_NAME="$1"
326   local FLEET_REPO_URL="$2"
327   local SW_CATALOGS_REPO_URL="$3"
328   local PROJECT_NAME="${4:-"${MGMT_PROJECT_NAME}"}"
329   local SW_CATALOGS_REPO_DIR="${5:-"${SW_CATALOGS_REPO_DIR}"}"
330
331   # Optional inputs:
332   # Paths for each profile in the Git repo
333   local INFRA_CONTROLLERS_PATH="${6:-"${PROJECT_NAME}/infra-controller-profiles/${CLUSTER_KUSTOMIZATION_NAME}"}"
334   local INFRA_CONFIGS_PATH="${7:-"${PROJECT_NAME}/infra-config-profiles/${CLUSTER_KUSTOMIZATION_NAME}"}"
335   local MANAGED_RESOURCES_PATH="${8:-"${PROJECT_NAME}/managed-resources/${CLUSTER_KUSTOMIZATION_NAME}"}"
336   local APPS_PATH="${9:-"${PROJECT_NAME}/app-profiles/${CLUSTER_KUSTOMIZATION_NAME}"}"
337
338   # Path for the source templates
339   local TEMPLATES="${SW_CATALOGS_REPO_DIR}/cloud-resources/flux-remote-bootstrap/cluster-base/templates"
340
341   # Generate
342   export CLUSTER_KUSTOMIZATION_NAME
343   export FLEET_REPO_URL
344   export SW_CATALOGS_REPO_URL
345   export INFRA_CONTROLLERS_PATH
346   export INFRA_CONFIGS_PATH
347   export MANAGED_RESOURCES_PATH
348   export APPS_PATH
349   join_lists \
350     <(cat) \
351     <(
352       folder2list \
353         "${TEMPLATES}" | \
354       replace_env_vars \
355         '${CLUSTER_KUSTOMIZATION_NAME},${FLEET_REPO_URL},${SW_CATALOGS_REPO_URL},${INFRA_CONTROLLERS_PATH},${INFRA_CONFIGS_PATH},${MANAGED_RESOURCES_PATH},${APPS_PATH}'
356     )
357 }
358
359
360 # Create SOPS configuration file for the root folder of the cluster
361 function create_sops_configuration_file_new_cluster() {
362   local PUBLIC_KEY="$1"
363
364   MANIFEST="creation_rules:
365   - encrypted_regex: ^(data|stringData)$
366     age: ${PUBLIC_KEY}
367   # - path_regex: .*.yaml
368   #   encrypted_regex: ^(data|stringData)$
369   #   age: ${PUBLIC_KEY}"
370
371   # Generate SOPS configuration file for the root folder
372   echo "${MANIFEST}"
373 }
374
375
376 # Generate K8s secret for management cluster storing secret age key for the new cluster
377 function generator_k8s_age_secret_new_cluster() {
378   local PRIVATE_KEY_NEW_CLUSTER="$1"
379   local PUBLIC_KEY_MGMT="$2"
380   local CLUSTER_AGE_SECRET_NAME="${3:-$(safe_name "sops-age-${CLUSTER_KUSTOMIZATION_NAME}")}"
381
382   join_lists \
383     <(cat) \
384     <(
385       echo "${PRIVATE_KEY_NEW_CLUSTER}" | \
386       grep -v '^#' | \
387       kubectl create secret generic "${CLUSTER_AGE_SECRET_NAME}" \
388         --namespace=managed-resources \
389         --from-file=agekey=/dev/stdin \
390         -o yaml --dry-run=client | \
391       encrypt_secret_from_stdin \
392         "${PUBLIC_KEY_MGMT}" |
393       manifest2list | \
394       set_filename_to_items "${CLUSTER_AGE_SECRET_NAME}.yaml"
395     )
396 }
397
398
399 # Generate bootstrap manifests for new cluster from the management cluster
400 function generator_bootstrap_new_cluster() {
401   local CLUSTER_NAME="$1"
402   local CLUSTER_KUSTOMIZATION_NAME="${2:$(safe_name ${CLUSTER_NAME})}"
403   local CLUSTER_AGE_SECRET_NAME="${3:-$(safe_name "sops-age-${CLUSTER_KUSTOMIZATION_NAME}")}"
404   local SW_CATALOGS_REPO_DIR="${4:-"${SW_CATALOGS_REPO_DIR}"}"
405
406   # Paths and names for the templates
407   local MANIFEST_FILENAME="${5:-"cluster-bootstrap-${CLUSTER_KUSTOMIZATION_NAME}.yaml"}"
408   local TEMPLATES="${6:-"${SW_CATALOGS_REPO_DIR}/cloud-resources/flux-remote-bootstrap/bootstrap/templates"}"
409   local TEMPLATE_MANIFEST_FILENAME="${7:-"remote-cluster-bootstrap.yaml"}"
410
411   # Generate manifests
412   export CLUSTER_KUSTOMIZATION_NAME
413   export CLUSTER_NAME
414   export CLUSTER_AGE_SECRET_NAME
415
416   join_lists \
417     <(cat) \
418     <(
419       folder2list \
420         "${TEMPLATES}" | \
421       rename_file_in_items \
422         "${TEMPLATE_MANIFEST_FILENAME}" \
423         "${MANIFEST_FILENAME}" | \
424       replace_env_vars \
425         '${CLUSTER_KUSTOMIZATION_NAME},${CLUSTER_NAME},${CLUSTER_AGE_SECRET_NAME}'
426       )
427 }
428
429
430 # Auxiliary function to create kustomization manifests
431 function manifest_kustomization() {
432   local KS_NAME="$1"
433   local KS_NS="$2"
434   local SOURCE_REPO="$3"
435   local MANIFESTS_PATH="$4"
436   local SOURCE_SYNC_INTERVAL="$5"
437   local HEALTH_CHECK_TO="$6"
438   local DEPENDS_ON="${7:-""}"
439   local OPTIONS="${8:-""}"
440
441   # Calculated inputs
442   local OPTION_FOR_DEPENDS_ON="$(
443     if [[ -z "${DEPENDS_ON}" ]];
444     then
445       echo ""
446     else
447       echo "--depends-on=${DEPENDS_ON}"
448     fi
449   )"
450   local OPTIONS="\
451     "${OPTIONS}" \
452     "${OPTION_FOR_DEPENDS_ON}" \
453   "
454
455   # Create Kustomization manifest
456   flux create kustomization "${KS_NAME}" \
457       --namespace="${KS_NS}" \
458       --source="${SOURCE_REPO}" \
459       --path="${MANIFESTS_PATH}" \
460       --interval="${SOURCE_SYNC_INTERVAL}" \
461       --health-check-timeout="${HEALTH_CHECK_TO}" \
462       ${OPTIONS} --export
463 }
464
465
466 # Helper function to generate a Kustomization
467 function generator_kustomization() {
468   local MANIFEST_FILENAME="$1"
469   local ALL_PARAMS=( "${@}" )
470   local PARAMS=( "${ALL_PARAMS[@]:1}" )
471
472   # Use manifest creator to become a generator
473   make_generator \
474     "${MANIFEST_FILENAME}" \
475     manifest_kustomization \
476       "${PARAMS[@]}"
477 }
478
479 # ----- END of Helper functions for remote cluster bootstrap -----
480
481
482 # Create bootstrap for remote cluster
483 function create_bootstrap_for_remote_cluster() {
484   local CLUSTER_NAME="$1"
485   local CLUSTER_KUSTOMIZATION_NAME="$2"
486   local FLEET_REPO_DIR="${3:-"${FLEET_REPO_DIR}"}"
487   local SW_CATALOGS_REPO_DIR="${4:-"${SW_CATALOGS_REPO_DIR}"}"
488   local FLEET_REPO_URL="${5:-""}"
489   local SW_CATALOGS_REPO_URL="${6:-""}"
490   local MGMT_PROJECT_NAME="${7:-${MGMT_PROJECT_NAME}}"
491   local PUBLIC_KEY_MGMT="${8:-"${PUBLIC_KEY_MGMT}"}"
492   local PUBLIC_KEY_NEW_CLUSTER="$9"
493   local PRIVATE_KEY_NEW_CLUSTER="${10:-${PRIVATE_KEY_NEW_CLUSTER}}"
494
495   # Calculates the folder where managed resources area defined
496   local MGMT_RESOURCES_DIR="${FLEET_REPO_DIR}/${MGMT_PROJECT_NAME}/managed-resources/_management"
497
498
499   # Create profile folders
500   echo "" | \
501   generator_profile_folders_new_cluster \
502     "${CLUSTER_KUSTOMIZATION_NAME}" \
503     "${FLEET_REPO_URL}" \
504     "${MGMT_PROJECT_NAME}" | \
505   list2folder_cp_over \
506     "${FLEET_REPO_DIR}"
507
508   # Create base Kustomizations for the new cluster
509   local CLUSTER_FOLDER="${FLEET_REPO_DIR}/clusters/${CLUSTER_KUSTOMIZATION_NAME}"
510   echo "" | \
511   generator_base_kustomizations_new_cluster \
512     "${CLUSTER_KUSTOMIZATION_NAME}" \
513     "${FLEET_REPO_URL}" \
514     "${SW_CATALOGS_REPO_URL}" \
515     "${MGMT_PROJECT_NAME}" \
516     "${SW_CATALOGS_REPO_DIR}" | \
517   list2folder_cp_over \
518     "${CLUSTER_FOLDER}"
519
520   # Add SOPS configuration at the root folder of the cluster
521   # NOTE: This file cannot be generated by pure KRM functions since it begins by a dot ('.')
522   create_sops_configuration_file_new_cluster \
523     "${PUBLIC_KEY_NEW_CLUSTER}" \
524   > "${CLUSTER_FOLDER}/.sops.yaml"
525
526   # Add also the public SOPS key to the repository so that others who clone the repo can encrypt new files
527   # NOTE: This file cannot be generated by pure KRM functions since it begins by a dot ('.')
528   echo "${PUBLIC_KEY_NEW_CLUSTER}" \
529   > "${CLUSTER_FOLDER}/.sops.pub.asc"
530
531   # Prepare everything to perform a Flux bootstrap of the new remote cluster from the management cluster.
532   # Here we also add the `age` private key to the **management cluster** as secret. This one will be used during bootstrap to inject the key into the new cluster
533   local CLUSTER_AGE_SECRET_NAME=$(safe_name "sops-age-${CLUSTER_KUSTOMIZATION_NAME}")
534   echo "" |
535   generator_bootstrap_new_cluster \
536     "${CLUSTER_NAME}" \
537     "${CLUSTER_KUSTOMIZATION_NAME}" \
538     "${CLUSTER_AGE_SECRET_NAME}" \
539     "${SW_CATALOGS_REPO_DIR}" | \
540   generator_k8s_age_secret_new_cluster \
541     "${PRIVATE_KEY_NEW_CLUSTER}" \
542     "${PUBLIC_KEY_MGMT}" \
543     "${CLUSTER_AGE_SECRET_NAME}" | \
544   prepend_folder_path "${CLUSTER_KUSTOMIZATION_NAME}/" | \
545   list2folder_cp_over \
546     "${MGMT_RESOURCES_DIR}"
547 }
548
549
550 # Create remote CrossPlane cluster (generic for any cloud)
551 function create_crossplane_cluster() {
552   local CLUSTER_KUSTOMIZATION_NAME="$1"
553   local CLUSTER_NAME="$2"
554   # As of today, one among `aks`, `eks` or `gke`:
555   local CLUSTER_TYPE="$3"
556   local PROVIDERCONFIG_NAME="${4:-default}"
557   local VM_SIZE="$5"
558   local NODE_COUNT="$6"
559   local CLUSTER_LOCATION="$7"
560   local K8S_VERSION="${8:-"'1.28'"}"
561   local PUBLIC_KEY_MGMT="${9:-"${PUBLIC_KEY_MGMT}"}"
562   local PUBLIC_KEY_NEW_CLUSTER="${10}"
563   local PRIVATE_KEY_NEW_CLUSTER="${11:-"${PRIVATE_KEY_NEW_CLUSTER}"}"
564   # AKS only
565   local AKS_RG_NAME="${12:-""}"
566   # GKE only
567   local GKE_PREEMPTIBLE_NODES="${13:-""}"
568   ## `FLEET_REPO_DIR` is the result of:
569   ## "{{inputs.parameters.fleet_mount_path}}/{{inputs.parameters.cloned_fleet_folder_name}}"
570   local FLEET_REPO_DIR="${14:-"${FLEET_REPO_DIR}"}"
571   local FLEET_REPO_URL="${15:-""}"
572   ## `SW_CATALOGS_REPO_DIR` is the result of:
573   ## "{{inputs.parameters.sw_catalogs_mount_path}}/{{inputs.parameters.cloned_sw_catalogs_folder_name}}"
574   local SW_CATALOGS_REPO_DIR="${16:-"${SW_CATALOGS_REPO_DIR}"}"
575   local SW_CATALOGS_REPO_URL="${17:-""}"
576   # Perform bootstrap unless asked otherwise
577   local SKIP_BOOTSTRAP="${18:"false"}"
578   # Only change if absolutely needeed
579   local MGMT_PROJECT_NAME="${19:-"osm_admin"}"
580   local MGMT_CLUSTER_NAME="${20:-"_management"}"
581   local BASE_TEMPLATES_PATH="${21:-"cloud-resources"}"
582   local TEMPLATE_MANIFEST_FILENAME="${22:-"${CLUSTER_TYPE,,}01.yaml"}"
583   local MANIFEST_FILENAME="${23:-"${CLUSTER_TYPE,,}-${CLUSTER_NAME}.yaml"}"
584
585
586   # Is the provider type supported?
587   local VALID_PROVIDERS=("eks" "aks" "gke")
588   CLUSTER_TYPE="${CLUSTER_TYPE,,}"
589   [[ ! ($(echo ${VALID_PROVIDERS[@]} | grep -w "${CLUSTER_TYPE}")) ]] && return 1
590
591   # Determines the source dir for the templates and the target folder in Fleet
592   local TEMPLATES_DIR="${SW_CATALOGS_REPO_DIR}/${BASE_TEMPLATES_PATH}/${CLUSTER_TYPE}/templates"
593   local TARGET_FOLDER="${FLEET_REPO_DIR}/${MGMT_PROJECT_NAME}/managed-resources/${MGMT_CLUSTER_NAME}"
594
595   # Determine which optional steps may be needed
596   local IS_AKS=$([[ "${CLUSTER_TYPE}" == "aks" ]]; echo $?)
597   local IS_GCP=$([[ "${CLUSTER_TYPE}" == "gcp" ]]; echo $?)
598
599   # Pipeline of transformations to create the cluster resource
600   export CLUSTER_KUSTOMIZATION_NAME
601   folder2list \
602     "${TEMPLATES_DIR}" | \
603   replace_env_vars \
604     '${CLUSTER_KUSTOMIZATION_NAME}' | \
605   patch_replace \
606     ".spec.postBuild.substitute.cluster_name" \
607     "${CLUSTER_NAME}" \
608     "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
609   patch_replace \
610     ".spec.postBuild.substitute.vm_size" \
611     "${VM_SIZE}" \
612     "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
613   patch_replace \
614     ".spec.postBuild.substitute.node_count" \
615     "${NODE_COUNT}" \
616     "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
617   patch_replace \
618     ".spec.postBuild.substitute.cluster_location" \
619     "${CLUSTER_LOCATION}" \
620     "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
621   patch_replace \
622     ".spec.postBuild.substitute.k8s_version" \
623     "${K8S_VERSION}" \
624     "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
625   patch_replace \
626     ".spec.postBuild.substitute.providerconfig_name" \
627     "${PROVIDERCONFIG_NAME}" \
628     "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
629   transform_if \
630     "${IS_AKS}" \
631     patch_replace \
632       ".spec.postBuild.substitute.rg_name" \
633       "${AKS_RG_NAME}" \
634       "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
635   transform_if \
636     "${IS_GKE}" \
637     patch_replace \
638       ".spec.postBuild.substitute.preemptible_nodes" \
639       "${GKE_PREEMPTIBLE_NODES}" \
640       "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
641   rename_file_in_items \
642     "${TEMPLATE_MANIFEST_FILENAME}" \
643     "${MANIFEST_FILENAME}" | \
644   prepend_folder_path "${CLUSTER_KUSTOMIZATION_NAME}/" | \
645   list2folder_cp_over \
646     "${TARGET_FOLDER}"
647
648   # Bootstrap (unless asked to skip)
649   if [[ "${SKIP_BOOTSTRAP,,}" == "true" ]]; then
650     return 0
651   fi
652   create_bootstrap_for_remote_cluster \
653     "${CLUSTER_NAME}" \
654     "${CLUSTER_KUSTOMIZATION_NAME}" \
655     "${FLEET_REPO_DIR}" \
656     "${SW_CATALOGS_REPO_DIR}" \
657     "${FLEET_REPO_URL}" \
658     "${SW_CATALOGS_REPO_URL}" \
659     "${MGMT_PROJECT_NAME}" \
660     "${PUBLIC_KEY_MGMT}" \
661     "${PUBLIC_KEY_NEW_CLUSTER}" \
662     "${PRIVATE_KEY_NEW_CLUSTER}"
663 }
664
665
666 # Delete remote cluster (generic for any cloud)
667 function delete_remote_cluster() {
668   local CLUSTER_KUSTOMIZATION_NAME="$1"
669   local PROJECT_NAME="${2:-"${MGMT_PROJECT_NAME}"}"
670   local FLEET_REPO_DIR="${3:-"${FLEET_REPO_DIR}"}"
671   local MGMT_RESOURCES_DIR="${4:-"${MGMT_RESOURCES_DIR}"}"
672
673   # Optional inputs: Paths for each profile in the Git repo
674   local INFRA_CONTROLLERS_DIR="${5:-"${FLEET_REPO_DIR}/${PROJECT_NAME}/infra-controller-profiles/${CLUSTER_KUSTOMIZATION_NAME}"}"
675   local INFRA_CONFIGS_DIR="${6:-"${FLEET_REPO_DIR}/${PROJECT_NAME}/infra-config-profiles/${CLUSTER_KUSTOMIZATION_NAME}"}"
676   local MANAGED_RESOURCES_DIR="${7:-"${FLEET_REPO_DIR}/${PROJECT_NAME}/managed-resources/${CLUSTER_KUSTOMIZATION_NAME}"}"
677   local APPS_DIR="${8:-"${FLEET_REPO_DIR}/${PROJECT_NAME}/app-profiles/${CLUSTER_KUSTOMIZATION_NAME}"}"
678   local CLUSTER_DIR="${9:-"${FLEET_REPO_DIR}/clusters/${CLUSTER_KUSTOMIZATION_NAME}"}"
679
680   # Delete profile folders
681   rm -rf "${INFRA_CONTROLLERS_DIR}"
682   rm -rf "${INFRA_CONFIGS_DIR}"
683   rm -rf "${MANAGED_RESOURCES_DIR}"
684   rm -rf "${APPS_DIR}"
685
686   # Delete base cluster Kustomizations
687   rm -rf "${CLUSTER_DIR}"
688
689   # Delete cluster resources
690   rm -rf "${MGMT_RESOURCES_DIR}/${CLUSTER_KUSTOMIZATION_NAME}"
691 }
692
693
694 # Update remote CrossPlane cluster (generic for any cloud)
695 function update_crossplane_cluster() {
696   local CLUSTER_KUSTOMIZATION_NAME="$1"
697   local CLUSTER_NAME="$2"
698   # As of today, one among `aks`, `eks` or `gke`:
699   local CLUSTER_TYPE="$3"
700   local PROVIDERCONFIG_NAME="${4:-default}"
701   local VM_SIZE="$5"
702   local NODE_COUNT="$6"
703   local CLUSTER_LOCATION="$7"
704   local K8S_VERSION="${8:-"'1.28'"}"
705   local PUBLIC_KEY_MGMT="${9:-"${PUBLIC_KEY_MGMT}"}"
706   local PUBLIC_KEY_NEW_CLUSTER="${10}"
707   local PRIVATE_KEY_NEW_CLUSTER="${11:-"${PRIVATE_KEY_NEW_CLUSTER}"}"
708   # AKS only
709   local AKS_RG_NAME="${12:-""}"
710   # GKE only
711   local GKE_PREEMPTIBLE_NODES="${13:-""}"
712   ## `FLEET_REPO_DIR` is the result of:
713   ## "{{inputs.parameters.fleet_mount_path}}/{{inputs.parameters.cloned_fleet_folder_name}}"
714   local FLEET_REPO_DIR="${14:-"${FLEET_REPO_DIR}"}"
715   local FLEET_REPO_URL="${15:-""}"
716   ## `SW_CATALOGS_REPO_DIR` is the result of:
717   ## "{{inputs.parameters.sw_catalogs_mount_path}}/{{inputs.parameters.cloned_sw_catalogs_folder_name}}"
718   local SW_CATALOGS_REPO_DIR="${16:-"${SW_CATALOGS_REPO_DIR}"}"
719   local SW_CATALOGS_REPO_URL="${17:-""}"
720   # Prevent a new bootstrap by default
721   local SKIP_BOOTSTRAP="${18:"true"}"
722   # Only change if absolutely needeed
723   local MGMT_PROJECT_NAME="${19:-"osm_admin"}"
724   local MGMT_CLUSTER_NAME="${20:-"_management"}"
725   local BASE_TEMPLATES_PATH="${21:-"cloud-resources"}"
726   local TEMPLATE_MANIFEST_FILENAME="${22:-"${CLUSTER_TYPE,,}01.yaml"}"
727   local MANIFEST_FILENAME="${23:-"${CLUSTER_TYPE,,}-${CLUSTER_NAME}.yaml"}"
728
729
730   # Is the provider type supported?
731   local VALID_PROVIDERS=("eks" "aks" "gke")
732   CLUSTER_TYPE="${CLUSTER_TYPE,,}"
733   [[ ! ($(echo ${VALID_PROVIDERS[@]} | grep -w "${CLUSTER_TYPE}")) ]] && return 1
734
735   # Determine key folders in Fleet
736   local MGMT_RESOURCES_DIR="${FLEET_REPO_DIR}/${MGMT_PROJECT_NAME}/managed-resources/${MGMT_CLUSTER_NAME}"
737
738   # First, delete cluster's CrossPlane resources
739   # NOTE: We only delete de Kustomization referring to CrossPlane resources,
740   # not the bootstrap resources or the profiles. Thus we avoid that KSUs are
741   # affected or a potential second unnecesary bootstrap.
742   rm -rf "${MGMT_RESOURCES_DIR}/${CLUSTER_KUSTOMIZATION_NAME}/${MANIFEST_FILENAME}"
743
744   # Then, recreate the manifests with updated values
745   create_crossplane_cluster \
746     "${CLUSTER_KUSTOMIZATION_NAME}" \
747     "${CLUSTER_NAME}" \
748     "${CLUSTER_TYPE}" \
749     "${PROVIDERCONFIG_NAME}" \
750     "${VM_SIZE}" \
751     "${NODE_COUNT}" \
752     "${CLUSTER_LOCATION}" \
753     "${K8S_VERSION}" \
754     "${PUBLIC_KEY_MGMT}" \
755     "${PUBLIC_KEY_NEW_CLUSTER}" \
756     "${PRIVATE_KEY_NEW_CLUSTER}" \
757     "${AKS_RG_NAME}" \
758     "${GKE_PREEMPTIBLE_NODES}" \
759     "${FLEET_REPO_DIR}" \
760     "${FLEET_REPO_URL}" \
761     "${SW_CATALOGS_REPO_DIR}" \
762     "${SW_CATALOGS_REPO_URL}" \
763     "${SKIP_BOOTSTRAP}" \
764     "${MGMT_PROJECT_NAME}" \
765     "${MGMT_CLUSTER_NAME}" \
766     "${BASE_TEMPLATES_PATH}" \
767     "${TEMPLATE_MANIFEST_FILENAME}" \
768     "${MANIFEST_FILENAME}"
769 }
770
771
772 # ----- Helper functions for adding/removing a profile from a cluster -----
773
774 # Helper function to find profiles of a given type already used in the cluster
775 function profiles_of_type_in_cluster() {
776   local CLUSTER_KUSTOMIZATION_NAME="$1"
777   local RELEVANT_PROFILE_TYPE="$2"
778   local FLEET_REPO_DIR="${3:-"${FLEET_REPO_DIR}"}"
779
780   # Calculated fields
781   local CLUSTER_FOLDER="${FLEET_REPO_DIR}/clusters/${CLUSTER_KUSTOMIZATION_NAME}"
782
783   # Processing (echoes the list)
784   folder2list \
785     "${CLUSTER_FOLDER}" | \
786   get_value_from_resourcelist \
787     ".metadata.name" \
788     "| select(.kind == \"Kustomization\")
789     | select(.metadata.labels.osm_profile_type == \"${RELEVANT_PROFILE_TYPE}\")" | \
790   multiline2commalist
791 }
792
793
794 # Function to list the profiles **this profile depends on**
795 function profiles_this_one_depends_on() {
796   local CLUSTER_KUSTOMIZATION_NAME="$1"
797   local PROFILE_TYPE="$2"
798   local FLEET_REPO_DIR="${3:-"${FLEET_REPO_DIR}"}"
799
800   case "${PROFILE_TYPE,,}" in
801
802     "controller" | "infra-controller" | "infra-controllers" | "infra_controller" | "infra_controllers")
803       # Controllers do not depend on any other type of profiles
804       echo ""
805       return 0
806       ;;
807
808     "config" | "infra-config" | "infra-configs" | "infra_config" | "infra_configs")
809       # Infra configs depend on controllers
810       profiles_of_type_in_cluster \
811         "${CLUSTER_KUSTOMIZATION_NAME}" \
812         "infra-controllers" \
813         "${FLEET_REPO_DIR}"
814       return 0
815       ;;
816
817     "managed" | "resources" | "managed-resources" | "managed_resources")
818       # Managed resources depend on infra configs
819       profiles_of_type_in_cluster \
820         "${CLUSTER_KUSTOMIZATION_NAME}" \
821         "infra-configs" \
822         "${FLEET_REPO_DIR}"
823       return 0
824       ;;
825
826      "app" |"apps" | "applications" | "cnf" | "cnfs" | "nf" | "nfs")
827       # Apps (also) depend on infra configs
828       profiles_of_type_in_cluster \
829         "${CLUSTER_KUSTOMIZATION_NAME}" \
830         "infra-configs" \
831         "${FLEET_REPO_DIR}"
832       return 0
833       ;;
834
835     *)
836       echo -n "------------ ERROR ------------"
837       return 1
838       ;;
839   esac
840 }
841
842
843 # Function to list the profiles that **depend on this profile**
844 function profiles_depend_on_this_one() {
845   local CLUSTER_KUSTOMIZATION_NAME="$1"
846   local PROFILE_TYPE="$2"
847   local FLEET_REPO_DIR="${3:-"${FLEET_REPO_DIR}"}"
848
849   case "${PROFILE_TYPE,,}" in
850
851     "controller" | "infra-controller" | "infra-controllers" | "infra_controller" | "infra_controllers")
852       # Infra configs depend on infra controllers
853       profiles_of_type_in_cluster \
854         "${CLUSTER_KUSTOMIZATION_NAME}" \
855         "infra-configs" \
856         "${FLEET_REPO_DIR}"
857       return 0
858       ;;
859
860     "config" | "infra-config" | "infra-configs" | "infra_config" | "infra_configs")
861       # Both managed resources and apps depend on configs
862       local PROFILES=(
863         $(
864           profiles_of_type_in_cluster \
865             "${CLUSTER_KUSTOMIZATION_NAME}" \
866             "managed-resources" \
867             "${FLEET_REPO_DIR}"
868         ) \
869         $(
870         profiles_of_type_in_cluster \
871           "${CLUSTER_KUSTOMIZATION_NAME}" \
872           "apps" \
873           "${FLEET_REPO_DIR}"
874         )
875       )
876       printf '%s,' "${PROFILES[@]}" | sed 's/,$//g'
877       return 0
878       ;;
879
880     "managed" | "resources" | "managed-resources" | "managed_resources")
881       # No other profiles depend on managed resources
882       echo ""
883       return 0
884       ;;
885
886      "app" |"apps" | "applications" | "cnf" | "cnfs" | "nf" | "nfs")
887       # No other profiles depend on apps
888       echo ""
889       return 0
890       ;;
891
892     *)
893       echo -n "------------ ERROR ------------"
894       return 1
895       ;;
896   esac
897 }
898
899
900 # Helper function to add a dependency to a Kustomization only if it does not exist already
901 function add_dependency_to_kustomization_safely() {
902   local KUSTOMIZATION_NAME="$1"
903   local KUSTOMIZATION_TO_ADD_AS_DEP="$2"
904
905   local INPUT=$(cat)
906   local FILTER="| select(.kind == \"Kustomization\") | select(.metadata.name == \"${KUSTOMIZATION_NAME}\")"
907
908   # Check if the dependency was added already
909   local TEST_RESULT=$(
910     echo "${INPUT}" | \
911     is_element_on_list \
912       ".spec.dependsOn[].name" \
913       "${KUSTOMIZATION_TO_ADD_AS_DEP}" \
914       "${FILTER}"
915   )
916
917   # If it existed already, returns the stream as is
918   if [[ "${TEST_RESULT}" == "true" ]]
919   then
920     echo "${INPUT}"
921   # Otherwise, processes the stream to add it
922   else
923     echo "${INPUT}" | \
924     patch_add_to_list \
925       ".spec.dependsOn" \
926       "{name: ${KUSTOMIZATION_TO_ADD_AS_DEP}}" \
927       "${FILTER}"
928   fi
929 }
930
931
932 # Helper function to remove a dependency from a Kustomization
933 function remove_dependency_from_kustomization_safely() {
934   local KUSTOMIZATION_NAME="$1"
935   local KUSTOMIZATION_TO_REMOVE_AS_DEP="$2"
936
937   # Calculated inputs
938   local KEY_PATH=".spec.dependsOn[] | select(.name == \"${KUSTOMIZATION_TO_REMOVE_AS_DEP}\")"
939   local FILTER="| select(.kind == \"Kustomization\") | select(.metadata.name == \"${KUSTOMIZATION_NAME}\")"
940
941   # Remove the entry from the dependency list (if it exists)
942   yq "del((.items[]${FILTER})${KEY_PATH})"
943 }
944
945
946 # Ensure list of Kustomizations depend on a given Kustomization
947 function add_dependency_to_set_of_kustomizations_safely() {
948   local KS_NAME="$1"
949   local THEY_DEPEND_ON_THIS="$2"
950
951   local INPUT="$(cat)"
952   local OUTPUT=""
953
954   # For each of the Kustomizations on the comma-separated list, adds `KS_NAME` as one of their dependencies
955   for KUST in ${THEY_DEPEND_ON_THIS//,/ }
956   do
957     local OUTPUT="$(
958       echo "${INPUT}" | \
959       add_dependency_to_kustomization_safely \
960         "${KUST}" \
961         "${KS_NAME}"
962     )"
963     local INPUT="${OUTPUT}"
964   done
965
966   # Return the final `ResultList`, after all iterations
967   echo "${OUTPUT}"
968 }
969
970
971 # Ensure list of Kustomizations no longer depend on a given Kustomization
972 function remove_dependency_from_set_of_kustomizations_safely() {
973   local KS_NAME="$1"
974   local THEY_NO_LONGER_DEPEND_ON_THIS="$2"
975
976   local INPUT="$(cat)"
977   local OUTPUT=""
978
979   # For each of the Kustomizations on the comma-separated list, removes `KS_NAME` from their dependencies
980   for KUST in ${THEY_NO_LONGER_DEPEND_ON_THIS//,/ }
981   do
982     local OUTPUT="$(
983       echo "${INPUT}" | \
984       remove_dependency_from_kustomization_safely \
985         "${KUST}" \
986         "${KS_NAME}"
987     )"
988     local INPUT="${OUTPUT}"
989   done
990
991   # Return the final `ResultList`, after all iterations
992   echo "${OUTPUT}"
993 }
994
995 # ----- END of Helper functions for adding/removing a profile from a cluster -----
996
997
998 # Add an existing profile to a cluster
999 function attach_profile_to_cluster() {
1000   local PROFILE_NAME="$1"
1001   local PROFILE_TYPE="$2"
1002   local PROJECT_NAME="$3"
1003   local CLUSTER_KUSTOMIZATION_NAME="$4"
1004   local FLEET_REPO_DIR="${5:-"${FLEET_REPO_DIR}"}"
1005
1006   # Calculated inputs
1007   local CLUSTER_FOLDER="${FLEET_REPO_DIR}/clusters/${CLUSTER_KUSTOMIZATION_NAME}"
1008   local TARGET_PROFILE_PATH="$(
1009       path_to_profile \
1010         "${PROFILE_NAME}" \
1011         "${PROFILE_TYPE}" \
1012         "${PROJECT_NAME}"
1013   )"
1014
1015   # Finds out which profiles it should depend on... and which profiles should depend on it
1016   local DEPENDS_ON=$(
1017     profiles_this_one_depends_on \
1018       "${CLUSTER_KUSTOMIZATION_NAME}" \
1019       "${PROFILE_TYPE}" \
1020       "${FLEET_REPO_DIR}"
1021   )
1022
1023   local THEY_DEPEND_ON_THIS=$(
1024     profiles_depend_on_this_one \
1025       "${CLUSTER_KUSTOMIZATION_NAME}" \
1026       "${PROFILE_TYPE}" \
1027       "${FLEET_REPO_DIR}"
1028   )
1029
1030   # Parameters for the new Kustomization object to point to the profile
1031   local KS_NAME="$(safe_name "${PROFILE_TYPE}-${PROFILE_NAME}")"
1032   local MANIFEST_FILENAME="${KS_NAME}.yaml"
1033   local KS_NS=flux-system
1034   local MANIFESTS_PATH="${TARGET_PROFILE_PATH}"
1035   local SOURCE_REPO=GitRepository/fleet-repo.flux-system
1036   local SOURCE_SYNC_INTERVAL="60m"
1037   local HEALTH_CHECK_TO="3m"
1038   local RETRY_INTERVAL="1m"
1039   local TIMEOUT="5m"
1040   local OPTIONS="\
1041     --decryption-provider=sops \
1042     --decryption-secret=sops-age \
1043     --prune=true \
1044     --timeout="${TIMEOUT}" \
1045     --retry-interval="${RETRY_INTERVAL}" \
1046     --label osm_profile_type="${PROFILE_TYPE}"
1047   "
1048
1049   # Finally, we update the folder with all the required changes:
1050   # - Update pre-existing Kustomizations that should depend on the new profile (besides others).
1051   # - Create a new Kustomization pointing to the profile.
1052   # - Update Kustomize's `kustomization.yaml` at the root of the cluster folder to take into account the new Kustomization pointing to the profile.
1053   # - Update the cluster folder accordingly.
1054   folder2list \
1055     "${CLUSTER_FOLDER}" |
1056   add_dependency_to_set_of_kustomizations_safely \
1057     "${KS_NAME}" \
1058     "${THEY_DEPEND_ON_THIS}" | \
1059   generator_kustomization \
1060     "${MANIFEST_FILENAME}" \
1061     "${KS_NAME}" \
1062     "${KS_NS}" \
1063     "${SOURCE_REPO}" \
1064     "${MANIFESTS_PATH}" \
1065     "${SOURCE_SYNC_INTERVAL}" \
1066     "${HEALTH_CHECK_TO}" \
1067     "${DEPENDS_ON}" \
1068     "${OPTIONS}" | \
1069   patch_add_to_list \
1070     ".resources" \
1071     "${MANIFEST_FILENAME}" \
1072     "| select(.kind == \"Kustomization\") | select(.apiVersion == \"kustomize.config.k8s.io/v1beta1\") | select(.metadata.annotations.\"config.kubernetes.io/path\" == \"kustomization.yaml\")" | \
1073   list2folder_sync_replace \
1074     "${CLUSTER_FOLDER}"
1075 }
1076
1077
1078 # Remove an existing profile from a cluster
1079 function detach_profile_from_cluster() {
1080   local PROFILE_NAME="$1"
1081   local PROFILE_TYPE="$2"
1082   local PROJECT_NAME="$3"
1083   local CLUSTER_KUSTOMIZATION_NAME="$4"
1084   local FLEET_REPO_DIR="${5:-"${FLEET_REPO_DIR}"}"
1085
1086   # Calculated inputs
1087   local CLUSTER_FOLDER="${FLEET_REPO_DIR}/clusters/${CLUSTER_KUSTOMIZATION_NAME}"
1088   local TARGET_PROFILE_PATH="$(
1089       path_to_profile \
1090         "${PROFILE_NAME}" \
1091         "${PROFILE_TYPE}" \
1092         "${PROJECT_NAME}"
1093   )"
1094
1095   # Finds out which profiles still depend on it
1096   local THEY_DEPEND_ON_THIS=$(
1097     profiles_depend_on_this_one \
1098       "${CLUSTER_KUSTOMIZATION_NAME}" \
1099       "${PROFILE_TYPE}" \
1100       "${FLEET_REPO_DIR}"
1101   )
1102
1103   # Parameters for the new Kustomization object to point to the profile
1104   local KS_NAME="$(safe_name "${PROFILE_TYPE}-${PROFILE_NAME}")"
1105
1106   # Finally, we update the folder with all the required changes:
1107   # - Update pre-existing Kustomizations that should depend on the new profile (besides others).
1108   # - Create a new Kustomization pointing to the profile.
1109   # - Update Kustomize's `kustomization.yaml` at the root of the cluster folder so that it no longer tries to gather the Kustomization pointing to the profile.
1110   # - Update the cluster folder accordingly.
1111   folder2list \
1112     "${CLUSTER_FOLDER}" |
1113   remove_dependency_from_set_of_kustomizations_safely \
1114     "${KS_NAME}" \
1115     "${THEY_DEPEND_ON_THIS}" | \
1116   delete_object \
1117     "${KS_NAME}" \
1118     "Kustomization" \
1119     "kustomize.toolkit.fluxcd.io/v1" | \
1120   patch_delete_from_list \
1121     ".resources[] | select(. == \"${MANIFEST_FILENAME}\") " \
1122     "| select(.kind == \"Kustomization\") | select(.apiVersion == \"kustomize.config.k8s.io/v1beta1\") | select(.metadata.annotations.\"config.kubernetes.io/path\" == \"kustomization.yaml\")" | \
1123   list2folder_sync_replace \
1124     "${CLUSTER_FOLDER}"
1125 }
1126
1127
1128 # Low-level function to add a KSU into a profile
1129 function create_ksu_into_profile() {
1130   local KSU_NAME="$1"
1131   local TARGET_PROFILE_FOLDER="$2"
1132   local TEMPLATES_PATH="$3"
1133   local SW_CATALOGS_REPO_DIR="$4"
1134   local TRANSFORMER="${5:-noop_transformer}"
1135
1136   # Gathers all optional parameters for transformer funcion (if any) and puts them into an array for further use
1137   local ALL_PARAMS=( "${@}" )
1138   local TRANSFORMER_ARGS=( "${ALL_PARAMS[@]:5}" )
1139
1140   # Composes the route to the local templates folder
1141   local TEMPLATES_FOLDER="${SW_CATALOGS_REPO_DIR}/${TEMPLATES_PATH}"
1142
1143   folder2list \
1144     "${TEMPLATES_FOLDER}" | \
1145   "${TRANSFORMER}" \
1146     "${TRANSFORMER_ARGS[@]}" | \
1147   prepend_folder_path "${KSU_NAME}/" | \
1148   list2folder_cp_over \
1149     "${TARGET_PROFILE_FOLDER}"
1150 }
1151
1152
1153 # Function to render a KSU from a `ResourceList` into a profile
1154 function render_ksu_into_profile() {
1155   local KSU_NAME="$1"
1156   local PROFILE_NAME="$2"
1157   local PROFILE_TYPE="$3"
1158   local PROJECT_NAME="${4:-"${MGMT_PROJECT_NAME}"}"
1159   local FLEET_REPO_DIR="$5"
1160   local SYNC="${6:-"false"}"
1161
1162   local TARGET_PROFILE_PATH=$(
1163     path_to_profile \
1164       "${PROFILE_NAME}" \
1165       "${PROFILE_TYPE}" \
1166       "${PROJECT_NAME}"
1167   )
1168
1169   local TARGET_PROFILE_FOLDER="${FLEET_REPO_DIR}/${TARGET_PROFILE_PATH}"
1170
1171   # Determines the appropriate function depending on rendering strategy
1172   # - Sync (and potentially delete files in target folder)
1173   # - Copy over (only overwrite changed files, keep the rest)
1174   RENDERER=""
1175   if [[ ${SYNC,,} == "true" ]];
1176   then
1177     RENDERER="list2folder_sync_replace"
1178   else
1179     RENDERER="list2folder_cp_over"
1180   fi
1181
1182   # Render with the selected strategy
1183   [[ "${DRY_RUN,,}" != "true" ]] && mkdir -p "${TARGET_PROFILE_FOLDER}/${KSU_NAME}"
1184   "${RENDERER}" \
1185     "${TARGET_PROFILE_FOLDER}/${KSU_NAME}"
1186   ## This is improves the behaviour of the following code,
1187   ## since avoids unintented deletions in parent folder due to sync
1188   # prepend_folder_path "${KSU_NAME}/" | \
1189   # "${RENDERER}" \
1190   #   "${TARGET_PROFILE_FOLDER}"
1191 }
1192
1193
1194 # High-level function to add a KSU into a profile for the case where
1195 # 1. It is originated from an OKA, and
1196 # 2. It is based on a HelmRelease.
1197 function create_hr_ksu_into_profile() {
1198   # Base KSU generation from template
1199   ## `TEMPLATES_DIR` is the result of:
1200   ## "{{inputs.parameters.sw_catalogs_mount_path}}/{{inputs.parameters.cloned_sw_catalogs_folder_name}}/{{inputs.parameters.templates_path}}"
1201   local TEMPLATES_DIR="$1"
1202   local SUBSTITUTE_ENVIRONMENT="${2:-"false"}"
1203   local SUBSTITUTION_FILTER="${3:-""}"
1204   local CUSTOM_ENV_VARS="${4:-""}"
1205   # Patch HelmRelease in KSU with inline values
1206   local KUSTOMIZATION_NAME="$5"
1207   local HELMRELEASE_NAME="$6"
1208   local INLINE_VALUES="${7:-""}"
1209   # Secret reference and generation (if required)
1210   local IS_PREEXISTING_SECRET="${8:-"false"}"
1211   local TARGET_NS="$9"
1212   local VALUES_SECRET_NAME="${10}"
1213   local SECRET_KEY="${11:-"values.yaml"}"
1214   local AGE_PUBLIC_KEY="${12}"
1215   ## `SECRET_VALUES` will be obtained from the
1216   ## secret named after the input parameter `reference_secret_for_values`,
1217   ## and from the key named after the input parameter `reference_key_for_values`
1218   local LOCAL_SECRET_VALUES="${13:-"${SECRET_VALUES}"}"
1219   # ConfigMap reference and generation (if required)
1220   local IS_PREEXISTING_CM="${14:-"false"}"
1221   local VALUES_CM_NAME="${15:-""}"
1222   local CM_KEY="${16:-""}"
1223   local CM_VALUES="${17:-""}"
1224   # KSU rendering
1225   local KSU_NAME="${18}"
1226   local PROFILE_NAME="${19}"
1227   local PROFILE_TYPE="${20}"
1228   local PROJECT_NAME="${21:-"osm_admin"}"
1229   ## `FLEET_REPO_DIR` is the result of:
1230   ## "{{inputs.parameters.fleet_mount_path}}/{{inputs.parameters.cloned_fleet_folder_name}}"
1231   local FLEET_REPO_DIR="${22:-"/fleet/fleet-osm/"}"
1232   local SYNC="${23:-"true"}"
1233
1234   # Decides which steps may be skipped
1235   HAS_INLINE_VALUES=$([[ -n "${INLINE_VALUES}" ]]; echo $?)
1236   HAS_REFERENCES=$([[ ( -n "${VALUES_SECRET_NAME}" ) || ( -n "${VALUES_CM_NAME}" ) ]]; echo $?)
1237   NEEDS_NEW_SECRET=$([[ ( -n "${VALUES_SECRET_NAME}" ) && ( "${IS_PREEXISTING_SECRET,,}" == "false" ) ]]; echo $?)
1238   NEEDS_NEW_CM=$([[ ( -n "${VALUES_CM_NAME}" ) && ( "${IS_PREEXISTING_CM,,}" == "false" ) ]]; echo $?)
1239   ECHO_RESOURCELIST=$([[ "${DEBUG,,}" == "true" ]]; echo $?)
1240
1241   # If applicable, loads additional environment variables
1242   if [[ -n "${CUSTOM_ENV_VARS}" ]];
1243   then
1244       set -a
1245       source <(echo "${CUSTOM_ENV_VARS}")
1246       set +a
1247   fi
1248
1249   # Runs workflow
1250   folder2list_generator \
1251     "${TEMPLATES_DIR}" \
1252     "${SUBSTITUTE_ENVIRONMENT}" \
1253     "${SUBSTITUTION_FILTER}" | \
1254   transform_if \
1255     "${HAS_INLINE_VALUES}" \
1256     add_values_to_helmrelease_via_ks \
1257       "${KUSTOMIZATION_NAME}" \
1258       "${HELMRELEASE_NAME}" \
1259       "${INLINE_VALUES}" | \
1260   transform_if \
1261     "${HAS_REFERENCES}" \
1262     add_ref_values_to_hr_via_ks \
1263       "${KUSTOMIZATION_NAME}" \
1264       "${HELMRELEASE_NAME}" \
1265       "${VALUES_SECRET_NAME}" \
1266       "${VALUES_CM_NAME}" | \
1267   transform_if \
1268     "${NEEDS_NEW_SECRET}" \
1269     make_generator \
1270       "hr-values-secret.yaml" \
1271       kubectl_encrypt \
1272         "${AGE_PUBLIC_KEY}" \
1273         create \
1274         secret \
1275         generic \
1276         "${VALUES_SECRET_NAME}" \
1277         --namespace="${TARGET_NS}" \
1278         --from-file="${SECRET_KEY}"=<(echo "${LOCAL_SECRET_VALUES}") \
1279         -o=yaml \
1280         --dry-run=client | \
1281   transform_if \
1282     "${NEEDS_NEW_CM}" \
1283     make_generator \
1284       "hr-values-configmap.yaml" \
1285       kubectl \
1286       create \
1287       configmap \
1288       "${VALUES_CM_NAME}" \
1289       --namespace="${TARGET_NS}" \
1290       --from-file="${SECRET_KEY}"=<(echo "${CM_VALUES}") \
1291       -o=yaml \
1292       --dry-run=client | \
1293   transform_if \
1294     "${ECHO_RESOURCELIST}" \
1295     tee /dev/stderr | \
1296   render_ksu_into_profile \
1297     "${KSU_NAME}" \
1298     "${PROFILE_NAME}" \
1299     "${PROFILE_TYPE}" \
1300     "${PROJECT_NAME}" \
1301     "${FLEET_REPO_DIR}" \
1302     "${SYNC}"
1303 }
1304
1305
1306 # High-level function to update a KSU for the case where
1307 # 1. It is originated from an OKA, and
1308 # 2. It is based on a HelmRelease.
1309 # NOTE: It is an alias of `create_hr_ksu_into_profile`, setting `sync` to true
1310 function update_hr_ksu_into_profile() {
1311   # Base KSU generation from template
1312   ## `TEMPLATES_DIR` is the result of:
1313   ## "{{inputs.parameters.sw_catalogs_mount_path}}/{{inputs.parameters.cloned_sw_catalogs_folder_name}}/{{inputs.parameters.templates_path}}"
1314   local TEMPLATES_DIR="$1"
1315   local SUBSTITUTE_ENVIRONMENT="${2:-"false"}"
1316   local SUBSTITUTION_FILTER="${3:-""}"
1317   local CUSTOM_ENV_VARS="${4:-""}"
1318   # Patch HelmRelease in KSU with inline values
1319   local KUSTOMIZATION_NAME="$5"
1320   local HELMRELEASE_NAME="$6"
1321   local INLINE_VALUES="${7:-""}"
1322   # Secret reference and generation (if required)
1323   local IS_PREEXISTING_SECRET="${8:-"false"}"
1324   local TARGET_NS="$9"
1325   local VALUES_SECRET_NAME="${10}"
1326   local SECRET_KEY="${11:-"values.yaml"}"
1327   local AGE_PUBLIC_KEY="${12}"
1328   ## `SECRET_VALUES` will be obtained from the
1329   ## secret named after the input parameter `reference_secret_for_values`,
1330   ## and from the key named after the input parameter `reference_key_for_values`
1331   local LOCAL_SECRET_VALUES="${13:-"${SECRET_VALUES}"}"
1332   # ConfigMap reference and generation (if required)
1333   local IS_PREEXISTING_CM="${14:-"false"}"
1334   local VALUES_CM_NAME="${15:-""}"
1335   local CM_KEY="${16:-""}"
1336   local CM_VALUES="${17:-""}"
1337   # KSU rendering
1338   local KSU_NAME="${18}"
1339   local PROFILE_NAME="${19}"
1340   local PROFILE_TYPE="${20}"
1341   local PROJECT_NAME="${21:-"osm_admin"}"
1342   ## `FLEET_REPO_DIR` is the result of:
1343   ## "{{inputs.parameters.fleet_mount_path}}/{{inputs.parameters.cloned_fleet_folder_name}}"
1344   local FLEET_REPO_DIR="${22:-"/fleet/fleet-osm/"}"
1345   # local SYNC="${23:-"true"}"
1346
1347
1348   # This function is just an alias of `create_hr_ksu_into_profile`
1349   # forcing synchronization over the KSU folder
1350   create_hr_ksu_into_profile \
1351     "${TEMPLATES_DIR}" \
1352     "${SUBSTITUTE_ENVIRONMENT}" \
1353     "${SUBSTITUTION_FILTER}" \
1354     "${CUSTOM_ENV_VARS}" \
1355     "${KUSTOMIZATION_NAME}" \
1356     "${HELMRELEASE_NAME}" \
1357     "${INLINE_VALUES}" \
1358     "${IS_PREEXISTING_SECRET}" \
1359     "${TARGET_NS}" \
1360     "${VALUES_SECRET_NAME}" \
1361     "${SECRET_KEY}" \
1362     "${AGE_PUBLIC_KEY}" \
1363     "${LOCAL_SECRET_VALUES}" \
1364     "${IS_PREEXISTING_CM}" \
1365     "${VALUES_CM_NAME}" \
1366     "${CM_KEY}" \
1367     "${CM_VALUES}" \
1368     "${KSU_NAME}" \
1369     "${PROFILE_NAME}" \
1370     "${PROFILE_TYPE}" \
1371     "${PROJECT_NAME}" \
1372     "${FLEET_REPO_DIR}" \
1373     "true"
1374 }
1375
1376
1377 # High-level function to create a "generated" KSU into a profile when:
1378 # 1. There is no template (OKA) available.
1379 # 2. The SW is based on a Helm Chart that we want to deploy.
1380 function create_generated_ksu_from_helm_into_profile() {
1381   # HelmRelease generation
1382   local HELMRELEASE_NAME="$1"
1383   local CHART_NAME="$2"
1384   local CHART_VERSION="$3"
1385   local TARGET_NS="$4"
1386   local CREATE_NS="${5:-"true"}"
1387   # Repo source generation
1388   local IS_PREEXISTING_REPO="${6:-"false"}"
1389   local HELMREPO_NAME="$7"
1390   local HELMREPO_URL="${8:-""}"
1391   local HELMREPO_NS="${9:-"${TARGET_NS}"}"
1392   local HELMREPO_SECRET_REF="${10:-""}"
1393   # HelmRelease inline values (if any)
1394   local INLINE_VALUES="${11:-""}"
1395   # Secret reference and generation (if required)
1396   local IS_PREEXISTING_SECRET="${12:-"false"}"
1397   local VALUES_SECRET_NAME="${13}"
1398   local SECRET_KEY="${14:-"values.yaml"}"
1399   local AGE_PUBLIC_KEY="${15}"
1400   ## `SECRET_VALUES` will be obtained from the
1401   ## secret named after the input parameter `reference_secret_for_values`,
1402   ## and from the key named after the input parameter `reference_key_for_values`
1403   local LOCAL_SECRET_VALUES="${16:-"${SECRET_VALUES}"}"
1404   # ConfigMap reference and generation (if required)
1405   local IS_PREEXISTING_CM="${17:-"false"}"
1406   local VALUES_CM_NAME="${18:-""}"
1407   local CM_KEY="${19:-""}"
1408   local CM_VALUES="${20:-""}"
1409   # KSU rendering
1410   local KSU_NAME="${21}"
1411   local PROFILE_NAME="${22}"
1412   local PROFILE_TYPE="${23}"
1413   local PROJECT_NAME="${24:-"osm_admin"}"
1414   ## `FLEET_REPO_DIR` is the result of:
1415   ## "{{inputs.parameters.fleet_mount_path}}/{{inputs.parameters.cloned_fleet_folder_name}}"
1416   local FLEET_REPO_DIR="${25:-"/fleet/fleet-osm/"}"
1417   # By default, it will not syncronize, so that we can easily accumulate more than
1418   # one Helm chart into the same KSU if desired
1419   local SYNC="${26:-"false"}"
1420
1421   # Decides which steps may be skipped
1422   local NEEDS_NEW_NS=$([[ "${CREATE_NS,,}" == "true" ]]; echo $?)
1423   local NEEDS_NEW_REPO_SOURCE=$([[ "${IS_PREEXISTING_REPO,,}" == "false" ]]; echo $?)
1424   local NEEDS_NEW_SECRET=$([[ ( -n "${VALUES_SECRET_NAME}" ) && ( "${IS_PREEXISTING_SECRET,,}" == "false" ) ]]; echo $?)
1425   local NEEDS_NEW_CM=$([[ ( -n "${VALUES_CM_NAME}" ) && ( "${IS_PREEXISTING_CM,,}" == "false" ) ]]; echo $?)
1426   local ECHO_RESOURCELIST=$([[ "${DEBUG,,}" == "true" ]]; echo $?)
1427
1428   # Determine extra options for HelmRelease creation and define full command
1429   OPTION_CHART_VERSION=""
1430   [[ -n "${CHART_VERSION}" ]] && OPTION_CHART_VERSION='--chart-version=${CHART_VERSION}'
1431   OPTION_INLINE_VALUES=""
1432   [[ -n "${INLINE_VALUES}" ]] && OPTION_INLINE_VALUES='--values=<(
1433     echo "${INLINE_VALUES}"
1434   )'
1435   OPTION_REFERENCE_SECRET=""
1436   [[ -n "${VALUES_SECRET_NAME}" ]] && OPTION_REFERENCE_SECRET='--values-from=Secret/${VALUES_SECRET_NAME}'
1437   OPTION_REFERENCE_CM=""
1438   [[ -n "${VALUES_CM_NAME}" ]] && OPTION_REFERENCE_CM='--values-from=ConfigMap/${VALUES_CM_NAME}'
1439
1440   export HR_COMMAND="\
1441     flux \
1442       -n "${TARGET_NS}" \
1443       create hr "${HELMRELEASE_NAME}" \
1444       --chart="${CHART_NAME}" \
1445       --source=HelmRepository/"${HELMREPO_NAME}.${HELMREPO_NS}" \
1446       "${OPTION_CHART_VERSION}" \
1447       "${OPTION_INLINE_VALUES}" \
1448       "${OPTION_REFERENCE_SECRET}" \
1449       "${OPTION_REFERENCE_CM}" \
1450       --export
1451   "
1452
1453   # Determine extra options for Helm source repo creation and define full command
1454   OPTION_REPO_SECRET=""
1455   [[ -n "${HELMREPO_SECRET_REF}" ]] && OPTION_REPO_SECRET='--secret-ref=${HELMREPO_SECRET_REF}'
1456
1457   export REPO_COMMAND="\
1458     flux \
1459       -n "${HELMREPO_NS}" \
1460       create source helm "${HELMREPO_NAME}" \
1461       --url="${HELMREPO_URL}" \
1462       "${OPTION_REPO_SECRET}" \
1463       --export
1464   "
1465
1466   # Runs workflow
1467   echo "" | \
1468   make_generator \
1469     "helm-release.yaml" \
1470     eval "${HR_COMMAND}" | \
1471   transform_if \
1472     "${NEEDS_NEW_NS}" \
1473     make_generator \
1474       "ns-for-hr.yaml" \
1475       kubectl \
1476       create \
1477       namespace \
1478       "${TARGET_NS}" \
1479       -o=yaml \
1480       --dry-run=client | \
1481   transform_if \
1482     "${NEEDS_NEW_REPO_SOURCE}" \
1483     make_generator \
1484       "helm-repo.yaml" \
1485       eval "${REPO_COMMAND}" | \
1486   transform_if \
1487     "${NEEDS_NEW_SECRET}" \
1488     make_generator \
1489       "hr-values-secret.yaml" \
1490       kubectl_encrypt \
1491         "${AGE_PUBLIC_KEY}" \
1492         create \
1493         secret \
1494         generic \
1495         "${VALUES_SECRET_NAME}" \
1496         --namespace="${TARGET_NS}" \
1497         --from-file="${SECRET_KEY}"=<(echo "${LOCAL_SECRET_VALUES}") \
1498         -o=yaml \
1499         --dry-run=client | \
1500   transform_if \
1501     "${NEEDS_NEW_CM}" \
1502     make_generator \
1503       "hr-values-configmap.yaml" \
1504       kubectl \
1505       create \
1506       configmap \
1507       "${VALUES_CM_NAME}" \
1508       --namespace="${TARGET_NS}" \
1509       --from-file="${SECRET_KEY}"=<(echo "${CM_VALUES}") \
1510       -o=yaml \
1511       --dry-run=client | \
1512   transform_if \
1513     "${ECHO_RESOURCELIST}" \
1514     tee /dev/stderr | \
1515   render_ksu_into_profile \
1516     "${KSU_NAME}" \
1517     "${PROFILE_NAME}" \
1518     "${PROFILE_TYPE}" \
1519     "${PROJECT_NAME}" \
1520     "${FLEET_REPO_DIR}" \
1521     "${SYNC}"
1522 }
1523
1524
1525 # High-level function to update a "generated" KSU:
1526 # 1. There is no template (OKA) available.
1527 # 2. The SW is based on a Helm Chart that we want to deploy.
1528 # NOTE: It is an alias of `create_generated_ksu_from_helm_into_profile`, setting `sync` to true
1529 function update_generated_ksu_from_helm_into_profile() {
1530   # HelmRelease generation
1531   local HELMRELEASE_NAME="$1"
1532   local CHART_NAME="$2"
1533   local CHART_VERSION="$3"
1534   local TARGET_NS="$4"
1535   local CREATE_NS="${5:-"true"}"
1536   # Repo source generation
1537   local IS_PREEXISTING_REPO="${6:-"false"}"
1538   local HELMREPO_NAME="$7"
1539   local HELMREPO_URL="${8:-""}"
1540   local HELMREPO_NS="${9:-"${TARGET_NS}"}"
1541   local HELMREPO_SECRET_REF="${10:-""}"
1542   # HelmRelease inline values (if any)
1543   local INLINE_VALUES="${11:-""}"
1544   # Secret reference and generation (if required)
1545   local IS_PREEXISTING_SECRET="${12:-"false"}"
1546   local VALUES_SECRET_NAME="${13}"
1547   local SECRET_KEY="${14:-"values.yaml"}"
1548   local AGE_PUBLIC_KEY="${15}"
1549   ## `SECRET_VALUES` will be obtained from the
1550   ## secret named after the input parameter `reference_secret_for_values`,
1551   ## and from the key named after the input parameter `reference_key_for_values`
1552   local LOCAL_SECRET_VALUES="${16:-"${SECRET_VALUES}"}"
1553   # ConfigMap reference and generation (if required)
1554   local IS_PREEXISTING_CM="${17:-"false"}"
1555   local VALUES_CM_NAME="${18:-""}"
1556   local CM_KEY="${19:-""}"
1557   local CM_VALUES="${20:-""}"
1558   # KSU rendering
1559   local KSU_NAME="${21}"
1560   local PROFILE_NAME="${22}"
1561   local PROFILE_TYPE="${23}"
1562   local PROJECT_NAME="${24:-"osm_admin"}"
1563   ## `FLEET_REPO_DIR` is the result of:
1564   ## "{{inputs.parameters.fleet_mount_path}}/{{inputs.parameters.cloned_fleet_folder_name}}"
1565   local FLEET_REPO_DIR="${25:-"/fleet/fleet-osm/"}"
1566   # By default, it will not syncronize, so that we can easily accumulate more than
1567   # one Helm chart into the same KSU if desired
1568   # local SYNC="${26:-"false"}"
1569
1570   # Decides which steps may be skipped
1571   local NEEDS_NEW_NS=$([[ "${CREATE_NS,,}" == "true" ]]; echo $?)
1572   local NEEDS_NEW_REPO_SOURCE=$([[ "${IS_PREEXISTING_REPO,,}" == "false" ]]; echo $?)
1573   local NEEDS_NEW_SECRET=$([[ ( -n "${VALUES_SECRET_NAME}" ) && ( "${IS_PREEXISTING_SECRET,,}" == "false" ) ]]; echo $?)
1574   local NEEDS_NEW_CM=$([[ ( -n "${VALUES_CM_NAME}" ) && ( "${IS_PREEXISTING_CM,,}" == "false" ) ]]; echo $?)
1575   local ECHO_RESOURCELIST=$([[ "${DEBUG,,}" == "true" ]]; echo $?)
1576
1577
1578   # This function is just an alias of `create_generated_ksu_from_helm_into_profile`
1579   # forcing synchronization over the KSU folder
1580   create_generated_ksu_from_helm_into_profile \
1581     "${HELMRELEASE_NAME}" \
1582     "${CHART_NAME}" \
1583     "${CHART_VERSION}" \
1584     "${TARGET_NS}" \
1585     "${CREATE_NS}" \
1586     "${IS_PREEXISTING_REPO}" \
1587     "${HELMREPO_NAME}" \
1588     "${HELMREPO_URL}" \
1589     "${HELMREPO_NS}" \
1590     "${HELMREPO_SECRET_REF}" \
1591     "${INLINE_VALUES}" \
1592     "${IS_PREEXISTING_SECRET}" \
1593     "${VALUES_SECRET_NAME}" \
1594     "${SECRET_KEY}" \
1595     "${AGE_PUBLIC_KEY}" \
1596     "${LOCAL_SECRET_VALUES}" \
1597     "${IS_PREEXISTING_CM}" \
1598     "${VALUES_CM_NAME}" \
1599     "${CM_KEY}" \
1600     "${CM_VALUES}" \
1601     "${KSU_NAME}" \
1602     "${PROFILE_NAME}" \
1603     "${PROFILE_TYPE}" \
1604     "${PROJECT_NAME}" \
1605     "${FLEET_REPO_DIR}" \
1606     "true"
1607 }
1608
1609
1610 # Low-level function to delete a KSU from a profile
1611 function delete_ksu_from_profile_path() {
1612   local KSU_NAME="$1"
1613   local TARGET_PROFILE_PATH="$2"
1614   local FLEET_REPO_DIR="${3:-"${FLEET_REPO_DIR}"}"
1615
1616   # Calculate profile folder
1617   TARGET_PROFILE_FOLDER="${FLEET_REPO_DIR}/${TARGET_PROFILE_PATH}"
1618
1619   # Delete the KSU folder
1620   rm -rf "${TARGET_PROFILE_FOLDER}/${KSU_NAME}"
1621 }
1622
1623
1624 # High-level function to delete a KSU from a profile
1625 function delete_ksu_from_profile() {
1626   local KSU_NAME="$1"
1627   local PROFILE_NAME="$2"
1628   local PROFILE_TYPE="$3"
1629   local PROJECT_NAME="${4:-"osm_admin"}"
1630   ## `FLEET_REPO_DIR` is the result of:
1631   ## "{{inputs.parameters.fleet_mount_path}}/{{inputs.parameters.cloned_fleet_folder_name}}"
1632   local FLEET_REPO_DIR="$5"
1633
1634   # Calculate profile folder
1635   local TARGET_PROFILE_PATH=$(
1636     path_to_profile \
1637       "${PROFILE_NAME}" \
1638       "${PROFILE_TYPE}" \
1639       "${PROJECT_NAME}"
1640   )
1641   TARGET_PROFILE_FOLDER="${FLEET_REPO_DIR}/${TARGET_PROFILE_PATH}"
1642
1643   # Delete the KSU folder
1644   rm -rf "${TARGET_PROFILE_FOLDER}/${KSU_NAME}"
1645 }
1646
1647
1648 # High-level function to clone a KSU from a profile to another
1649 function clone_ksu() {
1650   local SOURCE_KSU_NAME="$1"
1651   local SOURCE_PROFILE_NAME="$2"
1652   local SOURCE_PROFILE_TYPE="$3"
1653   local SOURCE_PROJECT_NAME="${4:-"osm_admin"}"
1654   local DESTINATION_KSU_NAME="${5:-"${SOURCE_KSU_NAME}"}"
1655   local DESTINATION_PROFILE_NAME="${6:-"${SOURCE_PROFILE_NAME}"}"
1656   local DESTINATION_PROFILE_TYPE="${7:-"${SOURCE_PROFILE_TYPE}"}"
1657   local DESTINATION_PROJECT_NAME="${8:-"${SOURCE_PROJECT_NAME}"}"
1658   ## `FLEET_REPO_DIR` is the result of:
1659   ## "{{inputs.parameters.fleet_mount_path}}/{{inputs.parameters.cloned_fleet_folder_name}}"
1660   local FLEET_REPO_DIR="$9"
1661
1662
1663   # If source and destination are identical, aborts
1664   if [[
1665     ("${SOURCE_KSU_NAME}" == "${DESTINATION_KSU_NAME}") && \
1666     ("${SOURCE_PROFILE_NAME}" == "${DESTINATION_PROFILE_NAME}") && \
1667     ("${SOURCE_PROFILE_TYPE}" == "${DESTINATION_PROFILE_TYPE}") && \
1668     ("${SOURCE_PROJECT_NAME}" == "${DESTINATION_PROJECT_NAME}") \
1669   ]];
1670   then
1671     return 1
1672   fi
1673
1674   # Calculate profile folders
1675   local SOURCE_PROFILE_PATH=$(
1676     path_to_profile \
1677       "${SOURCE_PROFILE_NAME}" \
1678       "${SOURCE_PROFILE_TYPE}" \
1679       "${SOURCE_PROJECT_NAME}"
1680   )
1681   local SOURCE_PROFILE_FOLDER="${FLEET_REPO_DIR}/${SOURCE_PROFILE_PATH}"
1682   local DESTINATION_PROFILE_PATH=$(
1683     path_to_profile \
1684       "${DESTINATION_PROFILE_NAME}" \
1685       "${DESTINATION_PROFILE_TYPE}" \
1686       "${DESTINATION_PROJECT_NAME}"
1687   )
1688   local DESTINATION_PROFILE_FOLDER="${FLEET_REPO_DIR}/${DESTINATION_PROFILE_PATH}"
1689
1690   # Clone KSU folder
1691   cp -ar \
1692     "${SOURCE_PROFILE_FOLDER}/${SOURCE_KSU_NAME}" \
1693     "${DESTINATION_PROFILE_FOLDER}/${DESTINATION_KSU_NAME}"
1694 }
1695
1696
1697 # Create a `ProviderConfig` for a CrossPlane provider
1698 function create_crossplane_providerconfig() {
1699   local PROVIDERCONFIG_NAME="$1"
1700   # As of today, one among `azure`, `aws` or `gcp`:
1701   local PROVIDER_TYPE="$2"
1702   local CRED_SECRET_NAME="$3"
1703   local CRED_SECRET_KEY="${4:-"creds"}"
1704   local CRED_SECRET_NS="${5:-"crossplane-system"}"
1705   # If empty, it assumes the secret already exists
1706   local CRED_SECRET_CONTENT="${6:-"${CRED_SECRET_CONTENT:-""}"}"
1707   local AGE_PUBLIC_KEY_MGMT="$7"
1708   ## `FLEET_REPO_DIR` is the result of:
1709   ## "{{inputs.parameters.fleet_mount_path}}/{{inputs.parameters.cloned_fleet_folder_name}}"
1710   local FLEET_REPO_DIR="${8:-"${FLEET_REPO_DIR}"}"
1711   ## `SW_CATALOGS_REPO_DIR` is the result of:
1712   ## "{{inputs.parameters.sw_catalogs_mount_path}}/{{inputs.parameters.cloned_sw_catalogs_folder_name}}"
1713   local SW_CATALOGS_REPO_DIR="${9:-"${SW_CATALOGS_REPO_DIR}"}"
1714   # Only when applicable
1715   local TARGET_GCP_PROJECT="${10:-""}"
1716   # Do not touch unless strictly needed
1717   local BASE_TEMPLATES_PATH="${11:-"infra-configs/crossplane/providers"}"
1718   local OSM_PROJECT_NAME="${12:-"osm_admin"}"
1719   local MGMT_CLUSTER_NAME="${13:-"_management"}"
1720
1721
1722   # Is the provider type supported?
1723   local VALID_PROVIDERS=("aws" "azure" "gcp")
1724   PROVIDER_TYPE="${PROVIDER_TYPE,,}"
1725   [[ ! ($(echo ${VALID_PROVIDERS[@]} | grep -w "${PROVIDER_TYPE}")) ]] && return 1
1726
1727   # Determines the source dir for the templates and the target folder in Fleet
1728   local TEMPLATES_DIR="${SW_CATALOGS_REPO_DIR}/${BASE_TEMPLATES_PATH}/${PROVIDER_TYPE}/templates"
1729   local TARGET_FOLDER="${FLEET_REPO_DIR}/${OSM_PROJECT_NAME}/infra-config-profiles/${MGMT_CLUSTER_NAME}/crossplane-providerconfigs/${PROVIDER_TYPE}"
1730
1731   # Determine which optional steps may be needed
1732   local NEEDS_NEW_SECRET=$([[ -n "${CRED_SECRET_CONTENT}" ]]; echo $?)
1733   local NEEDS_PROJECT_NAME=$([[ "${PROVIDER_TYPE}" == "gcp" ]]; echo $?)
1734
1735   # Renders the `ProviderConfig` manifest and the encrypted secret (if applicable)
1736   echo "" | \
1737   folder2list_generator \
1738     "${TEMPLATES_DIR}" | \
1739   patch_replace \
1740     ".metadata.name" \
1741     "${PROVIDERCONFIG_NAME}" \
1742     "| select(.kind == \"ProviderConfig\")" | \
1743   patch_replace \
1744     ".spec.credentials.secretRef.name" \
1745     "${CRED_SECRET_NAME}" \
1746     "| select(.kind == \"ProviderConfig\")" | \
1747   patch_replace \
1748     ".spec.credentials.secretRef.key" \
1749     "${CRED_SECRET_KEY}" \
1750     "| select(.kind == \"ProviderConfig\")" | \
1751   patch_replace \
1752     ".spec.credentials.secretRef.namespace" \
1753     "${CRED_SECRET_NS}" \
1754     "| select(.kind == \"ProviderConfig\")" | \
1755   transform_if \
1756     "${NEEDS_PROJECT_NAME}" \
1757     patch_replace \
1758       ".spec.projectID" \
1759       "${TARGET_GCP_PROJECT}" \
1760       "| select(.kind == \"ProviderConfig\")" | \
1761   transform_if \
1762     "${NEEDS_NEW_SECRET}" \
1763     make_generator \
1764       "credentials-secret.yaml" \
1765       kubectl_encrypt \
1766         "${AGE_PUBLIC_KEY_MGMT}" \
1767         create \
1768         secret \
1769         generic \
1770         "${CRED_SECRET_NAME}" \
1771         --namespace="${CRED_SECRET_NS}" \
1772         --from-file="${CRED_SECRET_KEY}"=<(echo "${CRED_SECRET_CONTENT}") \
1773         -o=yaml \
1774         --dry-run=client | \
1775   prepend_folder_path \
1776     "${PROVIDERCONFIG_NAME}/" | \
1777   list2folder_cp_over \
1778     "${TARGET_FOLDER}"
1779 }
1780
1781
1782 # Delete a `ProviderConfig` for a CrossPlane provider
1783 function delete_crossplane_providerconfig() {
1784   local PROVIDERCONFIG_NAME="$1"
1785   # As of today, one among `azure`, `aws` or `gcp`:
1786   local PROVIDER_TYPE="$2"
1787   ## `FLEET_REPO_DIR` is the result of:
1788   ## "{{inputs.parameters.fleet_mount_path}}/{{inputs.parameters.cloned_fleet_folder_name}}"
1789   local FLEET_REPO_DIR="${3:-"${FLEET_REPO_DIR}"}"
1790   # Do not touch unless strictly needed
1791   local OSM_PROJECT_NAME="${4:-"osm_admin"}"
1792   local MGMT_CLUSTER_NAME="${5:-"_management"}"
1793
1794
1795   # Is the provider type supported?
1796   local VALID_PROVIDERS=("aws" "azure" "gcp")
1797   PROVIDER_TYPE="${PROVIDER_TYPE,,}"
1798   [[ ! ($(echo ${VALID_PROVIDERS[@]} | grep -w "${PROVIDER_TYPE}")) ]] && return 1
1799
1800   # Determines the target folder in Fleet
1801   local PROVIDERCONFIG_FOLDER="${FLEET_REPO_DIR}/${OSM_PROJECT_NAME}/infra-config-profiles/${MGMT_CLUSTER_NAME}/crossplane-providerconfigs/${PROVIDER_TYPE}/${PROVIDERCONFIG_NAME}"
1802
1803   # Delete the folder
1804   rm -rf "${PROVIDERCONFIG_FOLDER}"
1805 }
1806
1807
1808 # Update a `ProviderConfig` for a CrossPlane provider
1809 function update_crossplane_providerconfig() {
1810   local PROVIDERCONFIG_NAME="$1"
1811   # As of today, one among `azure`, `aws` or `gcp`:
1812   local PROVIDER_TYPE="$2"
1813   local CRED_SECRET_NAME="$3"
1814   local CRED_SECRET_KEY="${4:-"creds"}"
1815   local CRED_SECRET_NS="${5:-"crossplane-system"}"
1816   # If empty, it assumes the secret already exists
1817   local CRED_SECRET_CONTENT="${6:-"${CRED_SECRET_CONTENT:-""}"}"
1818   local AGE_PUBLIC_KEY_MGMT="$7"
1819   ## `FLEET_REPO_DIR` is the result of:
1820   ## "{{inputs.parameters.fleet_mount_path}}/{{inputs.parameters.cloned_fleet_folder_name}}"
1821   local FLEET_REPO_DIR="${8:-"${FLEET_REPO_DIR}"}"
1822   ## `SW_CATALOGS_REPO_DIR` is the result of:
1823   ## "{{inputs.parameters.sw_catalogs_mount_path}}/{{inputs.parameters.cloned_sw_catalogs_folder_name}}"
1824   local SW_CATALOGS_REPO_DIR="${9:-"${SW_CATALOGS_REPO_DIR}"}"
1825   # Only when applicable
1826   local TARGET_GCP_PROJECT="${10:-""}"
1827   # Do not touch unless strictly needed
1828   local BASE_TEMPLATES_PATH="${11:-"infra-configs/crossplane/providers"}"
1829   local OSM_PROJECT_NAME="${12:-"osm_admin"}"
1830   local MGMT_CLUSTER_NAME="${13:-"_management"}"
1831
1832
1833   # Is the provider type supported?
1834   local VALID_PROVIDERS=("aws" "azure" "gcp")
1835   PROVIDER_TYPE="${PROVIDER_TYPE,,}"
1836   [[ ! ($(echo ${VALID_PROVIDERS[@]} | grep -w "${PROVIDER_TYPE}")) ]] && return 1
1837
1838   # First, delete; then, re-create
1839   delete_crossplane_providerconfig \
1840     "${PROVIDERCONFIG_NAME}" \
1841     "${PROVIDER_TYPE}" \
1842     "${FLEET_REPO_DIR}" \
1843     "${OSM_PROJECT_NAME}" \
1844     "${MGMT_CLUSTER_NAME}"
1845
1846   create_crossplane_providerconfig \
1847     "${PROVIDERCONFIG_NAME}" \
1848     "${PROVIDER_TYPE}" \
1849     "${CRED_SECRET_NAME}" \
1850     "${CRED_SECRET_KEY}" \
1851     "${CRED_SECRET_NS}" \
1852     "${CRED_SECRET_CONTENT}" \
1853     "${AGE_PUBLIC_KEY_MGMT}" \
1854     "${FLEET_REPO_DIR}" \
1855     "${SW_CATALOGS_REPO_DIR}" \
1856     "${TARGET_GCP_PROJECT}" \
1857     "${BASE_TEMPLATES_PATH}" \
1858     "${OSM_PROJECT_NAME}" \
1859     "${MGMT_CLUSTER_NAME}"
1860 }
1861
1862
1863 # Helper function to return the relative path of a location in SW Catalogs for an OKA
1864 function path_to_catalog() {
1865   local OKA_TYPE="$1"
1866   local PROJECT_NAME="${2:-"osm_admin"}"
1867
1868   # Corrects `osm_admin` project, since it uses the root folder
1869   PROJECT_NAME="${PROJECT_NAME}"
1870   [[ "${PROJECT_NAME}" == "osm_admin" ]] && PROJECT_NAME="."
1871
1872   # Echoes the relate path from the SW-Catalogs root
1873   case "${OKA_TYPE,,}" in
1874
1875     "controller" | "infra-controller" | "infra-controllers" | "infra_controller" | "infra_controllers")
1876       echo -n "${PROJECT_NAME}/infra-controllers"
1877       return 0
1878       ;;
1879
1880     "config" | "infra-config" | "infra-configs" | "infra_config" | "infra_configs")
1881       echo -n "${PROJECT_NAME}/infra-configs"
1882       return 0
1883       ;;
1884
1885     "managed" | "resources" | "managed-resources" | "managed_resources" | "cloud-resources" | "cloud_resources")
1886       echo -n "${PROJECT_NAME}/cloud-resources"
1887       return 0
1888       ;;
1889
1890      "app" |"apps" | "applications" | "cnf" | "cnfs" | "nf" | "nfs")
1891       echo -n "${PROJECT_NAME}/apps"
1892       return 0
1893       ;;
1894
1895     *)
1896       echo -n "------------ ERROR ------------"
1897       return 1
1898       ;;
1899   esac
1900 }
1901
1902
1903 # Create OKA of a specific kind
1904 function create_oka() {
1905   local OKA_NAME="$1"
1906   local OKA_TYPE="$2"
1907   local PROJECT_NAME="${3:-"."}"
1908   ## `SW_CATALOGS_REPO_DIR` is the result of:
1909   ## "{{inputs.parameters.sw_catalogs_mount_path}}/{{inputs.parameters.cloned_sw_catalogs_folder_name}}"
1910   local SW_CATALOGS_REPO_DIR="$4"
1911   local OKA_LOCATION="${5:-"."}"
1912   local TARBALL_FILE="${6:-"true"}"
1913
1914
1915   # Finds the corresponding catalog path from the SW-Catalogs root
1916   # and create the destination
1917   local CATALOG_PATH=$(\
1918     path_to_catalog \
1919       "${OKA_TYPE}" \
1920       "${PROJECT_NAME}"
1921   )
1922   local DESTINATION="${SW_CATALOGS_REPO_DIR}/${CATALOG_PATH}/${OKA_NAME}"
1923   mkdir -p "${DESTINATION}"
1924
1925   # When the OKA comes as a `tar.gz`
1926   if [[ "${TARBALL_FILE,,}" == "true" ]];
1927   then
1928     tar xvfz "${OKA_LOCATION}/${OKA_NAME}.tar.gz" -C "${DESTINATION}"
1929   else
1930   # Otherwise it must be a folder structure
1931     cp -var "${OKA_LOCATION}/${OKA_NAME}/*" "${DESTINATION}/"
1932   fi
1933 }
1934
1935
1936 # Delete OKA of a specific kind
1937 function delete_oka() {
1938   local OKA_NAME="$1"
1939   local OKA_TYPE="$2"
1940   local PROJECT_NAME="${3:-"."}"
1941   ## `SW_CATALOGS_REPO_DIR` is the result of:
1942   ## "{{inputs.parameters.sw_catalogs_mount_path}}/{{inputs.parameters.cloned_sw_catalogs_folder_name}}"
1943   local SW_CATALOGS_REPO_DIR="$4"
1944
1945
1946   # Finds the corresponding catalog path from the SW-Catalogs root
1947   # and determine the destination
1948   local CATALOG_PATH=$(\
1949     path_to_catalog \
1950       "${OKA_TYPE}" \
1951       "${PROJECT_NAME}"
1952   )
1953   local DESTINATION="${SW_CATALOGS_REPO_DIR}/${CATALOG_PATH}/${OKA_NAME}"
1954
1955   # Remove the folder
1956   rm -rf "${DESTINATION}"
1957 }
1958
1959
1960 # Update OKA of a specific kind
1961 function update_oka() {
1962   local OKA_NAME="$1"
1963   local OKA_TYPE="$2"
1964   local PROJECT_NAME="${3:-"."}"
1965   ## `SW_CATALOGS_REPO_DIR` is the result of:
1966   ## "{{inputs.parameters.sw_catalogs_mount_path}}/{{inputs.parameters.cloned_sw_catalogs_folder_name}}"
1967   local SW_CATALOGS_REPO_DIR="$4"
1968   local OKA_LOCATION="${5:-"."}"
1969   local TARBALL_FILE="${6:-"true"}"
1970
1971
1972   # Finds the corresponding catalog path from the SW-Catalogs root
1973   # and determine the destination
1974   local CATALOG_PATH=$(\
1975     path_to_catalog \
1976       "${OKA_TYPE}" \
1977       "${PROJECT_NAME}"
1978   )
1979   local DESTINATION="${SW_CATALOGS_REPO_DIR}/${CATALOG_PATH}/${OKA_NAME}"
1980
1981   # Remove and re-create
1982   rm -rf "${DESTINATION}"
1983   create_oka \
1984     "${OKA_NAME}" \
1985     "${OKA_TYPE}" \
1986     "${PROJECT_NAME}" \
1987     "${SW_CATALOGS_REPO_DIR}" \
1988     "${OKA_LOCATION}" \
1989     "${TARBALL_FILE}"
1990 }