blob: 04fde1a9f7d647ce35e765061f1f7c28fec9f9fe [file] [log] [blame]
garciadeblas70461c52024-07-03 09:17:56 +02001#!/bin/bash
garciadeblas36da51d2024-11-13 14:58:53 +01002#######################################################################################
3# Copyright ETSI Contributors and Others.
garciadeblas70461c52024-07-03 09:17:56 +02004#
garciadeblas36da51d2024-11-13 14:58:53 +01005# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
garciadeblas70461c52024-07-03 09:17:56 +02008#
garciadeblas36da51d2024-11-13 14:58:53 +01009# http://www.apache.org/licenses/LICENSE-2.0
garciadeblas70461c52024-07-03 09:17:56 +020010#
garciadeblas36da51d2024-11-13 14:58:53 +010011# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
14# implied.
15# See the License for the specific language governing permissions and
16# limitations under the License.
17#######################################################################################
18
19
garciadeblas70461c52024-07-03 09:17:56 +020020function generator_encrypted_secret_cloud_credentials() {
21 local CLOUD_CREDENTIALS_FILENAME="$1"
22 local SECRET_NAME="$2"
23 local PUBLIC_KEY="$3"
24 local SECRET_MANIFEST_FILENAME="${4:-secret-${SECRET_NAME}.yaml}"
25
26 join_lists \
27 <(cat) \
28 <(cat "${CREDENTIALS_DIR}/${CLOUD_CREDENTIALS_FILENAME}" | \
29 kubectl create secret generic ${SECRET_NAME} \
30 --namespace crossplane-system \
31 --from-file creds=/dev/stdin \
32 -o yaml --dry-run=client | \
33 encrypt_secret_from_stdin "${PUBLIC_KEY_MGMT}" | \
34 manifest2list | \
35 set_filename_to_items "${SECRET_MANIFEST_FILENAME}")
36}
37
38
39# Create ProviderConfig for Azure
40function add_providerconfig_for_azure() {
41 # Inputs
42 local CLOUD_CREDENTIALS="$1"
43 local NEW_SECRET_NAME="$2"
44 local PROVIDERCONFIG_NAME="${3:-default}"
45 local PUBLIC_KEY="${4:-${PUBLIC_KEY_MGMT}}"
46 local TARGET_FOLDER="${5:-${MGMT_ADDON_CONFIG_DIR}}"
47
48 # Path to folder with base templates
49 local TEMPLATES="${SW_CATALOGS_REPO_DIR}/infra-configs/crossplane/providers/azure/templates/"
50
51 # Pipeline
52 folder2list \
53 "${TEMPLATES}" | \
54 patch_replace \
55 ".metadata.name" \
56 "${PROVIDERCONFIG_NAME}" \
57 "| select(.kind == \"ProviderConfig\") | select(.metadata.name == \"default\")" | \
58 patch_replace \
59 ".spec.credentials.secretRef.name" \
60 "${NEW_SECRET_NAME}" \
61 "| select(.kind == \"ProviderConfig\") | select(.metadata.name == \"default\")" | \
62 rename_file_in_items \
63 "crossplane-providerconfig-azure.yaml" \
64 "crossplane-providerconfig-azure-${PROVIDERCONFIG_NAME}.yaml" | \
65 generator_encrypted_secret_cloud_credentials \
66 "${CLOUD_CREDENTIALS}" \
67 "${NEW_SECRET_NAME}" \
68 "${PUBLIC_KEY}" | \
69 list2folder_cp_over \
70 "${TARGET_FOLDER}"
71}
72
73
74# Create ProviderConfig for GCP
75function add_providerconfig_for_gcp() {
76 # Inputs
77 local CLOUD_CREDENTIALS="$1"
78 local NEW_SECRET_NAME="$2"
79 local GCP_PROJECT="$3"
80 local PROVIDERCONFIG_NAME="${4:-default}"
81 local PUBLIC_KEY="${5:-${PUBLIC_KEY_MGMT}}"
82 local TARGET_FOLDER="${6:-${MGMT_ADDON_CONFIG_DIR}}"
83
84 # Path to folder with base templates
85 local TEMPLATES="${SW_CATALOGS_REPO_DIR}/infra-configs/crossplane/providers/gcp/templates/"
86
87 # Pipeline
88 folder2list \
89 "${TEMPLATES}" | \
90 patch_replace \
91 ".metadata.name" \
92 "${PROVIDERCONFIG_NAME}" \
93 "| select(.kind == \"ProviderConfig\") | select(.metadata.name == \"default\")" | \
94 patch_replace \
95 ".spec.credentials.secretRef.name" \
96 "${NEW_SECRET_NAME}" \
97 "| select(.kind == \"ProviderConfig\") | select(.metadata.name == \"default\")" | \
98 patch_replace \
99 ".spec.projectID" \
100 "${GCP_PROJECT}" \
101 "| select(.kind == \"ProviderConfig\") | select(.metadata.name == \"default\")" | \
102 rename_file_in_items \
103 "crossplane-providerconfig-gcp.yaml" \
104 "crossplane-providerconfig-gcp-${PROVIDERCONFIG_NAME}.yaml" | \
105 generator_encrypted_secret_cloud_credentials \
106 "${CLOUD_CREDENTIALS}" \
107 "${NEW_SECRET_NAME}" \
108 "${PUBLIC_KEY}" | \
109 list2folder_cp_over \
110 "${TARGET_FOLDER}"
111}
112
113
yshah20619072025-06-13 14:56:25 +0000114# Create remote NodeGroup in AWS
115function create_nodegroup() {
116 local NODEGROUP_NAME="$1"
117 local NODEGROUP_KUSTOMIZATION_NAME="$2"
118 local CLUSTER_NAME="$3"
119 local CLUSTER_TYPE="$4"
120 local PROVIDERCONFIG_NAME="${5:-default}"
121 local VM_SIZE="$6"
122 local NODE_COUNT="$7"
123 local CLUSTER_LOCATION="$8"
124 local CONFIGMAP_NAME="${9}"
125 local NODEGROUP_ROLE="${10}"
126 local PUBLIC_KEY_MGMT="${11:-"${PUBLIC_KEY_MGMT}"}"
127 local PUBLIC_KEY_NEW_CLUSTER="${12}"
128 local PRIVATE_KEY_NEW_CLUSTER="${13:-"${PRIVATE_KEY_NEW_CLUSTER}"}"
129 local AKS_RG_NAME="${14:-""}"
130 local GKE_PREEMPTIBLE_NODES="${15:-""}"
131 local FLEET_REPO_DIR="${16:-"${FLEET_REPO_DIR}"}"
132 local FLEET_REPO_URL="${17:-""}"
133 local SW_CATALOGS_REPO_DIR="${18:-"${SW_CATALOGS_REPO_DIR}"}"
134 local SW_CATALOGS_REPO_URL="${19:-""}"
135 local SKIP_BOOTSTRAP="${20:"false"}"
136 local MGMT_PROJECT_NAME="${21:-"osm_admin"}"
137 local MGMT_CLUSTER_NAME="${22:-"_management"}"
138 local BASE_TEMPLATES_PATH="${23:-"cloud-resources"}"
139 local TEMPLATE_MANIFEST_FILENAME="${24:-"nodegroup.yaml"}"
140 local MANIFEST_FILENAME="${25:-"${NODEGROUP_NAME}.yaml"}"
141
142 # Is the provider type supported?
143 local VALID_PROVIDERS=("eks" "aks" "gke")
144 CLUSTER_TYPE="${CLUSTER_TYPE,,}"
145 [[ ! ($(echo ${VALID_PROVIDERS[@]} | grep -w "${CLUSTER_TYPE}")) ]] && return 1
146
147 # Determines the source dir for the templates and the target folder in Fleet
148 local TEMPLATES_DIR="${SW_CATALOGS_REPO_DIR}/${BASE_TEMPLATES_PATH}/eks-nodegroup/templates"
149 local TARGET_FOLDER="${FLEET_REPO_DIR}/${MGMT_PROJECT_NAME}/managed-resources/${MGMT_CLUSTER_NAME}/${CLUSTER_NAME}"
150
151 local IS_NODEGROUP_ROLE=$([[ "${NODEGROUP_ROLE}" != "default" ]]; echo $?)
152 local IS_DEFAULT_NODEGROUP_ROLE=$([[ "${NODEGROUP_ROLE}" == "default" ]]; echo $?)
153
154 local PATCH_VALUE=""
155 local COMPONENT=()
156 if [[ "${IS_NODEGROUP_ROLE}" == "0" ]];
157 then
158 PATCH_VALUE=$(cat <<EOF
159patch: |
160 apiVersion: eks.aws.upbound.io/v1beta1
161 kind: NodeGroup
162 metadata:
163 name: \${nodegroup_name}
164 spec:
165 forProvider:
166 nodeRoleArn: \${role}
167EOF
168)
169 else
170 COMPONENT=("../role")
171 fi
172
173 # Pipeline of transformations to create the cluster resource
174 export NODEGROUP_KUSTOMIZATION_NAME
175 # export OVERLAY_FOLDER
176 folder2list \
177 "${TEMPLATES_DIR}" | \
178 replace_env_vars \
179 '${NODEGROUP_KUSTOMIZATION_NAME}' | \
180 patch_replace \
181 ".spec.postBuild.substitute.nodegroup_name" \
182 "${NODEGROUP_NAME}" \
183 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${NODEGROUP_KUSTOMIZATION_NAME}\")" | \
184 patch_replace \
185 ".spec.postBuild.substitute.cluster_name" \
186 "${CLUSTER_NAME}" \
187 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${NODEGROUP_KUSTOMIZATION_NAME}\")" | \
188 patch_replace \
189 ".spec.postBuild.substitute.cluster_location" \
190 "${CLUSTER_LOCATION}" \
191 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${NODEGROUP_KUSTOMIZATION_NAME}\")" | \
192 patch_replace \
193 ".spec.postBuild.substitute.vm_size" \
194 "${VM_SIZE}" \
195 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${NODEGROUP_KUSTOMIZATION_NAME}\")" | \
196 patch_replace \
197 ".spec.postBuild.substitute.node_count" \
198 "${NODE_COUNT}" \
199 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${NODEGROUP_KUSTOMIZATION_NAME}\")" | \
200 patch_replace \
201 ".spec.postBuild.substitute.providerconfig_name" \
202 "${PROVIDERCONFIG_NAME}" \
203 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${NODEGROUP_KUSTOMIZATION_NAME}\")" | \
204 patch_replace \
205 ".spec.postBuild.substituteFrom[0].name" \
206 "${CONFIGMAP_NAME}" \
207 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${NODEGROUP_KUSTOMIZATION_NAME}\")" | \
208 patch_replace \
209 ".spec.postBuild.substitute.role" \
210 "${NODEGROUP_ROLE}" \
211 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${NODEGROUP_KUSTOMIZATION_NAME}\")" | \
212 transform_if \
213 "${IS_NODEGROUP_ROLE}" \
214 add_patch_to_kustomization_as_list \
215 "${NODEGROUP_KUSTOMIZATION_NAME}" \
216 "${PATCH_VALUE}" | \
217 transform_if \
218 "${IS_DEFAULT_NODEGROUP_ROLE}" \
219 add_component_to_kustomization_as_list \
220 "${NODEGROUP_KUSTOMIZATION_NAME}" \
221 "${COMPONENT}" | \
222 rename_file_in_items \
223 "${TEMPLATE_MANIFEST_FILENAME}" \
224 "${MANIFEST_FILENAME}" | \
225 prepend_folder_path "${NODEGROUP_KUSTOMIZATION_NAME}/" | \
226 list2folder_cp_over \
227 "${TARGET_FOLDER}"
228}
229
garciadeblas8a28f6d2025-06-11 11:11:56 +0200230
yshah20619072025-06-13 14:56:25 +0000231function scale_nodegroup() {
232 local NODEGROUP_NAME="$1"
233 local NODEGROUP_KUSTOMIZATION_NAME="$2"
234 local CLUSTER_NAME="$3"
235 local CLUSTER_TYPE="$4"
236 local NODE_COUNT="$5"
237 local PUBLIC_KEY_MGMT="${6:-"${PUBLIC_KEY_MGMT}"}"
238 local PUBLIC_KEY_NEW_CLUSTER="${7}"
239 local PRIVATE_KEY_NEW_CLUSTER="${8:-"${PRIVATE_KEY_NEW_CLUSTER}"}"
240 ## `FLEET_REPO_DIR` is the result of:
241 ## "{{inputs.parameters.fleet_mount_path}}/{{inputs.parameters.cloned_fleet_folder_name}}"
242 local FLEET_REPO_DIR="${9:-"${FLEET_REPO_DIR}"}"
243 local FLEET_REPO_URL="${10:-""}"
244 ## `SW_CATALOGS_REPO_DIR` is the result of:
245 ## "{{inputs.parameters.sw_catalogs_mount_path}}/{{inputs.parameters.cloned_sw_catalogs_folder_name}}"
246 local SW_CATALOGS_REPO_DIR="${11:-"${SW_CATALOGS_REPO_DIR}"}"
247 local SW_CATALOGS_REPO_URL="${12:-""}"
248 # Only change if absolutely needeed
249 local MGMT_PROJECT_NAME="${13:-"osm_admin"}"
250 local MGMT_CLUSTER_NAME="${14:-"_management"}"
251 local BASE_TEMPLATES_PATH="${15:-"cloud-resources"}"
252 local MANIFEST_FILENAME="${16:-"${NODEGROUP_NAME}"}"
253
garciadeblas8a28f6d2025-06-11 11:11:56 +0200254 # Is the provider type supported?
yshah20619072025-06-13 14:56:25 +0000255 local VALID_PROVIDERS=("eks" "aks" "gke")
256 CLUSTER_TYPE="${CLUSTER_TYPE,,}"
257 [[ ! ($(echo ${VALID_PROVIDERS[@]} | grep -w "${CLUSTER_TYPE}")) ]] && return 1
258
259 # Determines the folder in Fleet
260 local TARGET_FOLDER="${FLEET_REPO_DIR}/${MGMT_PROJECT_NAME}/managed-resources/${MGMT_CLUSTER_NAME}/${CLUSTER_NAME}/${NODEGROUP_KUSTOMIZATION_NAME}"
261
262 # Pipeline of transformations to create the cluster resource
263 export NODEGROUP_KUSTOMIZATION_NAME
264 folder2list \
265 "${TARGET_FOLDER}" | \
266 patch_replace \
267 ".spec.postBuild.substitute.node_count" \
268 "${NODE_COUNT}" \
269 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${NODEGROUP_KUSTOMIZATION_NAME}\")" | \
270 list2folder_cp_over \
271 "${TARGET_FOLDER}"
272}
273
garciadeblas8a28f6d2025-06-11 11:11:56 +0200274
yshah20619072025-06-13 14:56:25 +0000275# Delete nodegroup
276function delete_nodegroup() {
277 local NODEGROUP_KUSTOMIZATION_NAME="$1"
278 local CLUSTER_NAME="$2"
279 local PROJECT_NAME="${3:-"${MGMT_PROJECT_NAME}"}"
280 local FLEET_REPO_DIR="${4:-"${FLEET_REPO_DIR}"}"
281 local MGMT_RESOURCES_DIR="${5:-"${MGMT_RESOURCES_DIR}"}"
garciadeblas94e638f2025-08-06 17:53:22 +0200282
yshah20619072025-06-13 14:56:25 +0000283 local NODEGROUP_DIR="${MGMT_RESOURCES_DIR}/${CLUSTER_NAME}/${NODEGROUP_KUSTOMIZATION_NAME}"
garciadeblas94e638f2025-08-06 17:53:22 +0200284
yshah20619072025-06-13 14:56:25 +0000285 # Delete node Kustomizations
286 rm -rf "${NODEGROUP_DIR}"
287}
288
garciadeblas8a28f6d2025-06-11 11:11:56 +0200289
garciadeblas70461c52024-07-03 09:17:56 +0200290# TODO: Deprecated
291# Create AKS cluster (without bootstrap)
292function create_cluster_aks() {
293 local CLUSTER_NAME="$1"
294 local VM_SIZE="$2"
295 local NODE_COUNT="$3"
296 local CLUSTER_LOCATION="$4"
297 local RG_NAME="$5"
298 local K8S_VERSION="${6:-"'1.28'"}"
299 local PROVIDERCONFIG_NAME="${7:-default}"
300 local CLUSTER_KUSTOMIZATION_NAME="${8:$(safe_name ${CLUSTER_NAME})}"
301 local TARGET_FOLDER="${9:-${MGMT_RESOURCES_DIR}}"
302 local MANIFEST_FILENAME="${10:-"${CLUSTER_NAME}.yaml"}"
303 local TEMPLATES="${11:-"${SW_CATALOGS_REPO_DIR}/cloud-resources/aks/templates/"}"
304 local TEMPLATE_MANIFEST_FILENAME="${12:-"aks01.yaml"}"
305
306 export CLUSTER_KUSTOMIZATION_NAME
307 folder2list \
308 "${TEMPLATES}" | \
309 replace_env_vars \
310 '${CLUSTER_KUSTOMIZATION_NAME}' | \
311 patch_replace \
312 ".spec.postBuild.substitute.cluster_name" \
313 "${CLUSTER_NAME}" \
314 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
315 patch_replace \
316 ".spec.postBuild.substitute.cluster_name" \
317 "${CLUSTER_NAME}" \
318 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
319 patch_replace \
320 ".spec.postBuild.substitute.vm_size" \
321 "${VM_SIZE}" \
322 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
323 patch_replace \
324 ".spec.postBuild.substitute.node_count" \
325 "${NODE_COUNT}" \
326 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
327 patch_replace \
328 ".spec.postBuild.substitute.cluster_location" \
329 "${CLUSTER_LOCATION}" \
330 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
331 patch_replace \
332 ".spec.postBuild.substitute.rg_name" \
333 "${RG_NAME}" \
334 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
335 patch_replace \
336 ".spec.postBuild.substitute.k8s_version" \
337 "${K8S_VERSION}" \
338 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
339 patch_replace \
340 ".spec.postBuild.substitute.providerconfig_name" \
341 "${PROVIDERCONFIG_NAME}" \
342 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
343 rename_file_in_items \
344 "${TEMPLATE_MANIFEST_FILENAME}" \
345 "${MANIFEST_FILENAME}" | \
346 prepend_folder_path "${CLUSTER_KUSTOMIZATION_NAME}/" | \
347 list2folder_cp_over \
348 "${TARGET_FOLDER}"
349}
350
351
352# Generator to create a profile folder
353function generator_profile_folder() {
354 local CONFIGMAP_NAME="$1"
355 local PROFILE_PATH="$2"
356 local PROFILE_TYPE="$3"
357 local REPO_URL="${4:-${FLEET_REPO_URL}}"
358 local PROFILE_LOCAL_DIR="${5:-"${PROFILE_PATH}"}"
359
360 join_lists \
361 <(cat) \
362 <(kubectl create configmap $(safe_name "${CONFIGMAP_NAME}") \
363 --namespace flux-system \
364 --from-literal=repo="${REPO_URL}" \
365 --from-literal=path="${PROFILE_PATH}" \
366 -o yaml \
367 --dry-run=client | \
368 manifest2list | \
369 set_label \
370 "osm_profile_type" \
371 "${PROFILE_TYPE}" | \
372 set_filename_to_items "profile-configmap.yaml" | \
373 prepend_folder_path "${PROFILE_LOCAL_DIR}/")
374}
375
376
377# Helper function to return the relative path of a profile
378function path_to_profile() {
379 local PROFILE_NAME="$1"
380 local PROFILE_TYPE="$2"
381 local PROJECT_NAME="${3:-"${MGMT_PROJECT_NAME}"}"
382
383 case "${PROFILE_TYPE,,}" in
384
385 "controller" | "infra-controller" | "infra-controllers" | "infra_controller" | "infra_controllers")
386 echo -n "${PROJECT_NAME}/infra-controller-profiles/${PROFILE_NAME}"
387 return 0
388 ;;
389
390 "config" | "infra-config" | "infra-configs" | "infra_config" | "infra_configs")
391 echo -n "${PROJECT_NAME}/infra-config-profiles/${PROFILE_NAME}"
392 return 0
393 ;;
394
395 "managed" | "resources" | "managed-resources" | "managed_resources")
396 echo -n "${PROJECT_NAME}/managed-resources/${PROFILE_NAME}"
397 return 0
398 ;;
399
400 "app" |"apps" | "applications" | "cnf" | "cnfs" | "nf" | "nfs")
401 echo -n "${PROJECT_NAME}/app-profiles/${PROFILE_NAME}"
402 return 0
403 ;;
404
405 *)
406 echo -n "------------ ERROR ------------"
407 return 1
408 ;;
409 esac
410}
411
412
413# Function to create a new profile
414function create_profile() {
415 local PROFILE_NAME="$1"
416 local PROFILE_TYPE="$2"
417 local PROJECT_NAME="${3:-"${MGMT_PROJECT_NAME}"}"
418 local FLEET_REPO_URL="${4:-"${FLEET_REPO_URL}"}"
419 local FLEET_REPO_DIR="${5:-"${FLEET_REPO_DIR}"}"
420
421 local TARGET_PROFILE_PATH="$(
422 path_to_profile \
423 "${PROFILE_NAME}" \
424 "${PROFILE_TYPE}" \
425 "${PROJECT_NAME}" \
426 )"
427
428 # Generate profile as `ResourceList` and render to target folder.
429 echo "" | \
430 generator_profile_folder \
431 "${PROFILE_NAME}-${PROFILE_TYPE}" \
432 "${TARGET_PROFILE_PATH}" \
433 "${PROFILE_TYPE}" \
434 "${FLEET_REPO_URL}" \
435 "." | \
436 list2folder_cp_over \
437 "${FLEET_REPO_DIR}/${TARGET_PROFILE_PATH}"
438}
439
440
441# Function to delete a profile
442function delete_profile() {
443 local PROFILE_NAME="$1"
444 local PROFILE_TYPE="$2"
445 local PROJECT_NAME="${3:-"${MGMT_PROJECT_NAME}"}"
446 local FLEET_REPO_DIR="${4:-"${FLEET_REPO_DIR}"}"
447
448 local TARGET_PROFILE_PATH="$(
449 path_to_profile \
450 "${PROFILE_NAME}" \
451 "${PROFILE_TYPE}" \
452 "${PROJECT_NAME}" \
453 )"
454
455 # Delete the profile folder
456 rm -rf "${FLEET_REPO_DIR}/${TARGET_PROFILE_PATH}"
457}
458
459
460# ----- BEGIN of Helper functions for remote cluster bootstrap -----
461
462# Generate structure of profile folders prior to bootstrap
463function generator_profile_folders_new_cluster() {
464 # Inputs
465 local PROFILE_NAME="$1"
466 local FLEET_REPO_URL="$2"
467 local PROJECT_NAME="${3:-"${MGMT_PROJECT_NAME}"}"
468 # Optional inputs: Paths for each profile in the Git repo
469 local INFRA_CONTROLLERS_PATH="${4:-"${PROJECT_NAME}/infra-controller-profiles/${PROFILE_NAME}"}"
470 local INFRA_CONFIGS_PATH="${5:-"${PROJECT_NAME}/infra-config-profiles/${PROFILE_NAME}"}"
471 local MANAGED_RESOURCES_PATH="${6:-"${PROJECT_NAME}/managed-resources/${PROFILE_NAME}"}"
472 local APPS_PATH="${7:-"${PROJECT_NAME}/app-profiles/${PROFILE_NAME}"}"
473
474 # Generate profiles as `ResourceList`. merging with inputs
475 join_lists \
476 <(cat) \
477 <(
478 echo "" | \
479 generator_profile_folder \
480 "${PROFILE_NAME}-profile-infra-controllers" \
481 "${INFRA_CONTROLLERS_PATH}" \
482 "infra-controllers" \
483 "${FLEET_REPO_URL}" | \
484 generator_profile_folder \
485 "${PROFILE_NAME}-profile-infra-configs" \
486 "${INFRA_CONFIGS_PATH}" \
487 "infra-configs" \
488 "${FLEET_REPO_URL}" | \
489 generator_profile_folder \
490 "${PROFILE_NAME}-profile-managed-resources" \
491 "${MANAGED_RESOURCES_PATH}" \
492 "managed-resources" \
493 "${FLEET_REPO_URL}" | \
494 generator_profile_folder \
495 "${PROFILE_NAME}-profile-apps" \
496 "${APPS_PATH}" \
497 "apps" \
498 "${FLEET_REPO_URL}"
499 )
500}
501
502
503# Generate base Flux Kustomizations for the new cluster prior to bootstrap
504function generator_base_kustomizations_new_cluster() {
505 local CLUSTER_KUSTOMIZATION_NAME="$1"
506 local FLEET_REPO_URL="$2"
507 local SW_CATALOGS_REPO_URL="$3"
508 local PROJECT_NAME="${4:-"${MGMT_PROJECT_NAME}"}"
509 local SW_CATALOGS_REPO_DIR="${5:-"${SW_CATALOGS_REPO_DIR}"}"
garciadeblas94e638f2025-08-06 17:53:22 +0200510
garciadeblas70461c52024-07-03 09:17:56 +0200511 # Optional inputs:
512 # Paths for each profile in the Git repo
garciadeblas94e638f2025-08-06 17:53:22 +0200513 local INFRA_CONTROLLERS_PATH="${6:-"${PROJECT_NAME}/infra-controller-profiles/${CLUSTER_KUSTOMIZATION_NAME}"}"
514 local INFRA_CONFIGS_PATH="${7:-"${PROJECT_NAME}/infra-config-profiles/${CLUSTER_KUSTOMIZATION_NAME}"}"
515 local MANAGED_RESOURCES_PATH="${8:-"${PROJECT_NAME}/managed-resources/${CLUSTER_KUSTOMIZATION_NAME}"}"
516 local APPS_PATH="${9:-"${PROJECT_NAME}/app-profiles/${CLUSTER_KUSTOMIZATION_NAME}"}"
517
518 # Path for the source templates
519 local TEMPLATES="${SW_CATALOGS_REPO_DIR}/cloud-resources/flux-remote-bootstrap/cluster-base/templates"
garciadeblas70461c52024-07-03 09:17:56 +0200520
521 # Generate
522 export CLUSTER_KUSTOMIZATION_NAME
523 export FLEET_REPO_URL
524 export SW_CATALOGS_REPO_URL
525 export INFRA_CONTROLLERS_PATH
526 export INFRA_CONFIGS_PATH
527 export MANAGED_RESOURCES_PATH
528 export APPS_PATH
529 join_lists \
530 <(cat) \
531 <(
532 folder2list \
533 "${TEMPLATES}" | \
534 replace_env_vars \
535 '${CLUSTER_KUSTOMIZATION_NAME},${FLEET_REPO_URL},${SW_CATALOGS_REPO_URL},${INFRA_CONTROLLERS_PATH},${INFRA_CONFIGS_PATH},${MANAGED_RESOURCES_PATH},${APPS_PATH}'
536 )
537}
538
539
540# Create SOPS configuration file for the root folder of the cluster
541function create_sops_configuration_file_new_cluster() {
542 local PUBLIC_KEY="$1"
543
544 MANIFEST="creation_rules:
545 - encrypted_regex: ^(data|stringData)$
546 age: ${PUBLIC_KEY}
547 # - path_regex: .*.yaml
548 # encrypted_regex: ^(data|stringData)$
549 # age: ${PUBLIC_KEY}"
550
551 # Generate SOPS configuration file for the root folder
552 echo "${MANIFEST}"
553}
554
555
556# Generate K8s secret for management cluster storing secret age key for the new cluster
557function generator_k8s_age_secret_new_cluster() {
558 local PRIVATE_KEY_NEW_CLUSTER="$1"
559 local PUBLIC_KEY_MGMT="$2"
560 local CLUSTER_AGE_SECRET_NAME="${3:-$(safe_name "sops-age-${CLUSTER_KUSTOMIZATION_NAME}")}"
garciadeblas8a28f6d2025-06-11 11:11:56 +0200561 local CLUSTER_AGE_SECRET_NAMESPACE="${4:-"managed-resources"}"
garciadeblas70461c52024-07-03 09:17:56 +0200562
563 join_lists \
564 <(cat) \
565 <(
566 echo "${PRIVATE_KEY_NEW_CLUSTER}" | \
567 grep -v '^#' | \
568 kubectl create secret generic "${CLUSTER_AGE_SECRET_NAME}" \
garciadeblas8a28f6d2025-06-11 11:11:56 +0200569 --namespace="${CLUSTER_AGE_SECRET_NAMESPACE}" \
garciadeblas70461c52024-07-03 09:17:56 +0200570 --from-file=agekey=/dev/stdin \
571 -o yaml --dry-run=client | \
572 encrypt_secret_from_stdin \
573 "${PUBLIC_KEY_MGMT}" |
574 manifest2list | \
575 set_filename_to_items "${CLUSTER_AGE_SECRET_NAME}.yaml"
576 )
577}
578
579
580# Generate bootstrap manifests for new cluster from the management cluster
581function generator_bootstrap_new_cluster() {
582 local CLUSTER_NAME="$1"
garciadeblas9cd70f52025-07-31 18:06:51 +0200583 local CLUSTER_KUSTOMIZATION_NAME="${2:-$(safe_name ${CLUSTER_NAME})}"
garciadeblas70461c52024-07-03 09:17:56 +0200584 local CLUSTER_AGE_SECRET_NAME="${3:-$(safe_name "sops-age-${CLUSTER_KUSTOMIZATION_NAME}")}"
585 local SW_CATALOGS_REPO_DIR="${4:-"${SW_CATALOGS_REPO_DIR}"}"
586
587 # Paths and names for the templates
garciadeblas94e638f2025-08-06 17:53:22 +0200588 local MANIFEST_FILENAME="${5:-"cluster-bootstrap-${CLUSTER_KUSTOMIZATION_NAME}.yaml"}"
589 local TEMPLATES="${6:-"${SW_CATALOGS_REPO_DIR}/cloud-resources/flux-remote-bootstrap/bootstrap/templates"}"
590 local TEMPLATE_MANIFEST_FILENAME="${7:-"remote-cluster-bootstrap.yaml"}"
garciadeblas70461c52024-07-03 09:17:56 +0200591
592 # Generate manifests
593 export CLUSTER_KUSTOMIZATION_NAME
594 export CLUSTER_NAME
595 export CLUSTER_AGE_SECRET_NAME
596
597 join_lists \
598 <(cat) \
599 <(
600 folder2list \
601 "${TEMPLATES}" | \
602 rename_file_in_items \
603 "${TEMPLATE_MANIFEST_FILENAME}" \
604 "${MANIFEST_FILENAME}" | \
605 replace_env_vars \
garciadeblas94e638f2025-08-06 17:53:22 +0200606 '${CLUSTER_KUSTOMIZATION_NAME},${CLUSTER_NAME},${CLUSTER_AGE_SECRET_NAME}'
garciadeblas70461c52024-07-03 09:17:56 +0200607 )
608}
609
610
611# Auxiliary function to create kustomization manifests
612function manifest_kustomization() {
613 local KS_NAME="$1"
614 local KS_NS="$2"
615 local SOURCE_REPO="$3"
616 local MANIFESTS_PATH="$4"
617 local SOURCE_SYNC_INTERVAL="$5"
618 local HEALTH_CHECK_TO="$6"
619 local DEPENDS_ON="${7:-""}"
620 local OPTIONS="${8:-""}"
621
622 # Calculated inputs
623 local OPTION_FOR_DEPENDS_ON="$(
624 if [[ -z "${DEPENDS_ON}" ]];
625 then
626 echo ""
627 else
628 echo "--depends-on=${DEPENDS_ON}"
629 fi
630 )"
631 local OPTIONS="\
632 "${OPTIONS}" \
633 "${OPTION_FOR_DEPENDS_ON}" \
634 "
635
636 # Create Kustomization manifest
637 flux create kustomization "${KS_NAME}" \
638 --namespace="${KS_NS}" \
639 --source="${SOURCE_REPO}" \
640 --path="${MANIFESTS_PATH}" \
641 --interval="${SOURCE_SYNC_INTERVAL}" \
642 --health-check-timeout="${HEALTH_CHECK_TO}" \
643 ${OPTIONS} --export
644}
645
646
647# Helper function to generate a Kustomization
648function generator_kustomization() {
649 local MANIFEST_FILENAME="$1"
650 local ALL_PARAMS=( "${@}" )
651 local PARAMS=( "${ALL_PARAMS[@]:1}" )
652
653 # Use manifest creator to become a generator
654 make_generator \
655 "${MANIFEST_FILENAME}" \
656 manifest_kustomization \
657 "${PARAMS[@]}"
658}
659
660# ----- END of Helper functions for remote cluster bootstrap -----
661
662
663# Create bootstrap for remote cluster
664function create_bootstrap_for_remote_cluster() {
665 local CLUSTER_NAME="$1"
666 local CLUSTER_KUSTOMIZATION_NAME="$2"
667 local FLEET_REPO_DIR="${3:-"${FLEET_REPO_DIR}"}"
668 local SW_CATALOGS_REPO_DIR="${4:-"${SW_CATALOGS_REPO_DIR}"}"
669 local FLEET_REPO_URL="${5:-""}"
670 local SW_CATALOGS_REPO_URL="${6:-""}"
671 local MGMT_PROJECT_NAME="${7:-${MGMT_PROJECT_NAME}}"
672 local PUBLIC_KEY_MGMT="${8:-"${PUBLIC_KEY_MGMT}"}"
673 local PUBLIC_KEY_NEW_CLUSTER="$9"
674 local PRIVATE_KEY_NEW_CLUSTER="${10:-${PRIVATE_KEY_NEW_CLUSTER}}"
garciadeblas36da51d2024-11-13 14:58:53 +0100675 local IMPORTED_CLUSTER="${11:-"false"}"
garciadeblas94e638f2025-08-06 17:53:22 +0200676
garciadeblas36da51d2024-11-13 14:58:53 +0100677
garciadeblas8a28f6d2025-06-11 11:11:56 +0200678 # Calculates the folder where managed resources are defined
garciadeblas94e638f2025-08-06 17:53:22 +0200679 local MGMT_RESOURCES_DIR="${FLEET_REPO_DIR}/${MGMT_PROJECT_NAME}/managed-resources/_management"
garciadeblas70461c52024-07-03 09:17:56 +0200680
garciadeblas70461c52024-07-03 09:17:56 +0200681 # Create profile folders
682 echo "" | \
683 generator_profile_folders_new_cluster \
684 "${CLUSTER_KUSTOMIZATION_NAME}" \
685 "${FLEET_REPO_URL}" \
686 "${MGMT_PROJECT_NAME}" | \
687 list2folder_cp_over \
688 "${FLEET_REPO_DIR}"
689
690 # Create base Kustomizations for the new cluster
691 local CLUSTER_FOLDER="${FLEET_REPO_DIR}/clusters/${CLUSTER_KUSTOMIZATION_NAME}"
692 echo "" | \
693 generator_base_kustomizations_new_cluster \
694 "${CLUSTER_KUSTOMIZATION_NAME}" \
695 "${FLEET_REPO_URL}" \
696 "${SW_CATALOGS_REPO_URL}" \
697 "${MGMT_PROJECT_NAME}" \
garciadeblas94e638f2025-08-06 17:53:22 +0200698 "${SW_CATALOGS_REPO_DIR}" | \
garciadeblas70461c52024-07-03 09:17:56 +0200699 list2folder_cp_over \
700 "${CLUSTER_FOLDER}"
701
702 # Add SOPS configuration at the root folder of the cluster
703 # NOTE: This file cannot be generated by pure KRM functions since it begins by a dot ('.')
704 create_sops_configuration_file_new_cluster \
705 "${PUBLIC_KEY_NEW_CLUSTER}" \
706 > "${CLUSTER_FOLDER}/.sops.yaml"
707
708 # Add also the public SOPS key to the repository so that others who clone the repo can encrypt new files
709 # NOTE: This file cannot be generated by pure KRM functions since it begins by a dot ('.')
710 echo "${PUBLIC_KEY_NEW_CLUSTER}" \
711 > "${CLUSTER_FOLDER}/.sops.pub.asc"
712
713 # Prepare everything to perform a Flux bootstrap of the new remote cluster from the management cluster.
714 # 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
715 local CLUSTER_AGE_SECRET_NAME=$(safe_name "sops-age-${CLUSTER_KUSTOMIZATION_NAME}")
716 echo "" |
717 generator_bootstrap_new_cluster \
718 "${CLUSTER_NAME}" \
719 "${CLUSTER_KUSTOMIZATION_NAME}" \
720 "${CLUSTER_AGE_SECRET_NAME}" \
garciadeblas94e638f2025-08-06 17:53:22 +0200721 "${SW_CATALOGS_REPO_DIR}" | \
garciadeblas70461c52024-07-03 09:17:56 +0200722 generator_k8s_age_secret_new_cluster \
723 "${PRIVATE_KEY_NEW_CLUSTER}" \
724 "${PUBLIC_KEY_MGMT}" \
garciadeblas94e638f2025-08-06 17:53:22 +0200725 "${CLUSTER_AGE_SECRET_NAME}" | \
garciadeblas70461c52024-07-03 09:17:56 +0200726 prepend_folder_path "${CLUSTER_KUSTOMIZATION_NAME}/" | \
727 list2folder_cp_over \
728 "${MGMT_RESOURCES_DIR}"
garciadeblas36da51d2024-11-13 14:58:53 +0100729
730 # If it is an imported cluster, we must create a placeholder Kustomization
731 if [[ "${IMPORTED_CLUSTER,,}" == "true" ]];
732 then
733 TEMPLATES_DIR="${SW_CATALOGS_REPO_DIR}/cloud-resources/flux-remote-bootstrap/empty-kustomization/templates"
734
735 export CLUSTER_KUSTOMIZATION_NAME
736 folder2list \
737 "${TEMPLATES_DIR}" | \
738 replace_env_vars \
739 '${CLUSTER_KUSTOMIZATION_NAME}' | \
740 prepend_folder_path "${CLUSTER_KUSTOMIZATION_NAME}/" | \
741 list2folder_cp_over \
742 "${MGMT_RESOURCES_DIR}"
743 fi
744}
745
746
747# Disconnect Flux of remote cluster
748function disconnect_flux_remote_cluster() {
749 local CLUSTER_KUSTOMIZATION_NAME="$1"
750 local FLEET_REPO_DIR="${2:-"${FLEET_REPO_DIR}"}"
751 local MGMT_PROJECT_NAME="${3:-${MGMT_PROJECT_NAME}}"
752
753
754 # Calculates key folders
755 ## Base folder with Kustomizations for the new cluster
garciadeblasc4afd542025-06-18 17:37:59 +0200756 # local CLUSTER_FOLDER="${FLEET_REPO_DIR}/clusters/${CLUSTER_KUSTOMIZATION_NAME}"
garciadeblas36da51d2024-11-13 14:58:53 +0100757 ## Folder where managed resources are defined in the management cluster
758 local MGMT_RESOURCES_CLUSTER_DIR="${FLEET_REPO_DIR}/${MGMT_PROJECT_NAME}/managed-resources/_management/${CLUSTER_KUSTOMIZATION_NAME}"
759
garciadeblasc4afd542025-06-18 17:37:59 +0200760 # Delete Flux resources synchronized directly from remote cluster
761 # rm -rf "${CLUSTER_FOLDER}/flux-system"
garciadeblas36da51d2024-11-13 14:58:53 +0100762
763 # Delete Flux resources bootstraped remotely
764 rm -rf "${MGMT_RESOURCES_CLUSTER_DIR}/cluster-bootstrap-${CLUSTER_KUSTOMIZATION_NAME}.yaml"
garciadeblas70461c52024-07-03 09:17:56 +0200765}
766
767
768# Create remote CrossPlane cluster (generic for any cloud)
769function create_crossplane_cluster() {
770 local CLUSTER_KUSTOMIZATION_NAME="$1"
771 local CLUSTER_NAME="$2"
772 # As of today, one among `aks`, `eks` or `gke`:
773 local CLUSTER_TYPE="$3"
774 local PROVIDERCONFIG_NAME="${4:-default}"
775 local VM_SIZE="$5"
776 local NODE_COUNT="$6"
777 local CLUSTER_LOCATION="$7"
778 local K8S_VERSION="${8:-"'1.28'"}"
779 local PUBLIC_KEY_MGMT="${9:-"${PUBLIC_KEY_MGMT}"}"
780 local PUBLIC_KEY_NEW_CLUSTER="${10}"
781 local PRIVATE_KEY_NEW_CLUSTER="${11:-"${PRIVATE_KEY_NEW_CLUSTER}"}"
782 # AKS only
783 local AKS_RG_NAME="${12:-""}"
784 # GKE only
785 local GKE_PREEMPTIBLE_NODES="${13:-""}"
786 ## `FLEET_REPO_DIR` is the result of:
787 ## "{{inputs.parameters.fleet_mount_path}}/{{inputs.parameters.cloned_fleet_folder_name}}"
788 local FLEET_REPO_DIR="${14:-"${FLEET_REPO_DIR}"}"
789 local FLEET_REPO_URL="${15:-""}"
790 ## `SW_CATALOGS_REPO_DIR` is the result of:
791 ## "{{inputs.parameters.sw_catalogs_mount_path}}/{{inputs.parameters.cloned_sw_catalogs_folder_name}}"
792 local SW_CATALOGS_REPO_DIR="${16:-"${SW_CATALOGS_REPO_DIR}"}"
793 local SW_CATALOGS_REPO_URL="${17:-""}"
794 # Perform bootstrap unless asked otherwise
795 local SKIP_BOOTSTRAP="${18:"false"}"
796 # Only change if absolutely needeed
797 local MGMT_PROJECT_NAME="${19:-"osm_admin"}"
798 local MGMT_CLUSTER_NAME="${20:-"_management"}"
799 local BASE_TEMPLATES_PATH="${21:-"cloud-resources"}"
rshri4240a7d2025-06-13 11:30:35 +0000800 # EKS only
801 local CLUSTER_IAM_ROLE="${22}"
802 local CLUSTER_PRIVATE_SUBNETS_ID="${23}"
803 local CLUSTER_PUBLIC_SUBNETS_ID="${24}"
804 local CONFIGMAP_NAME="${25}"
805 local TEMPLATE_MANIFEST_FILENAME="${26:-"${CLUSTER_TYPE,,}01.yaml"}"
806 local MANIFEST_FILENAME="${27:-"${CLUSTER_TYPE,,}-${CLUSTER_NAME}.yaml"}"
garciadeblas70461c52024-07-03 09:17:56 +0200807
garciadeblas70461c52024-07-03 09:17:56 +0200808 # Is the provider type supported?
809 local VALID_PROVIDERS=("eks" "aks" "gke")
810 CLUSTER_TYPE="${CLUSTER_TYPE,,}"
811 [[ ! ($(echo ${VALID_PROVIDERS[@]} | grep -w "${CLUSTER_TYPE}")) ]] && return 1
812
rshri4240a7d2025-06-13 11:30:35 +0000813 # Determine which optional steps may be needed
814 local IS_EKS=$([[ "${CLUSTER_TYPE}" == "eks" ]]; echo $?)
815 local IS_AKS=$([[ "${CLUSTER_TYPE}" == "aks" ]]; echo $?)
816 local IS_GCP=$([[ "${CLUSTER_TYPE}" == "gcp" ]]; echo $?)
817
818 local IS_EKS_AND_IAM=1
819 local IAM_COMPONENTS=()
820 local PATCH_SUBNET=0
821 local PATCH_IAM=0
822 local PATCH_VALUE=""
823 local PATCH=1
824 local CONFIG=1
825
826 if [[ "$IS_EKS" -eq 0 ]]; then
827
828 # Check for subnet config
829 if [[ "$CLUSTER_PRIVATE_SUBNETS_ID" == "default" ]]; then
830 IS_EKS_AND_IAM=0
831 IAM_COMPONENTS+=("../network")
832 else
833 PATCH_SUBNET=1
834 fi
835
836 # Check for IAM role config
837 if [[ "$CLUSTER_IAM_ROLE" == "default" ]]; then
838 IS_EKS_AND_IAM=0
839 IAM_COMPONENTS+=("../iam")
840 else
841 PATCH_IAM=1
842 fi
843
844 # Set PATCH flag if patch is required
845 if [[ $PATCH_SUBNET -eq 1 || $PATCH_IAM -eq 1 ]]; then
846 # PATCH=1
847 echo "Generating patch..."
848
849 PATCH_VALUE=$(cat <<EOF
850 patch: |
851 apiVersion: eks.aws.upbound.io/v1beta1
852 kind: Cluster
853 metadata:
854 name: \${cluster_resource_name}-cluster
855 spec:
856 forProvider:
857EOF
858 )
859
860 # Append subnet block if needed
861 if [[ $PATCH_SUBNET -eq 1 ]]; then
862 PATCH_VALUE+=$(cat <<EOF
863
864 vpcConfig:
865 - endpointPrivateAccess: true
866 endpointPublicAccess: true
867 subnetIds: \${private_subnets}
868EOF
869 )
870 fi
871
872 # Append IAM role block if needed
873 if [[ $PATCH_IAM -eq 1 ]]; then
874 PATCH_VALUE+=$(cat <<EOF
875
876 roleArn: \${cluster_iam_role}
877EOF
878 )
879 fi
880 fi
881
882 # Set PATCH flag
883 if [[ "$PATCH_SUBNET" -eq 1 || "$PATCH_IAM" -eq 1 ]]; then
884 PATCH=0
885 fi
886
887 # Set CONFIG flag
888 if [[ "$CONFIGMAP_NAME" != "default" ]]; then
889 CONFIG=0
890 fi
891 fi
892
garciadeblas70461c52024-07-03 09:17:56 +0200893 # Determines the source dir for the templates and the target folder in Fleet
894 local TEMPLATES_DIR="${SW_CATALOGS_REPO_DIR}/${BASE_TEMPLATES_PATH}/${CLUSTER_TYPE}/templates"
895 local TARGET_FOLDER="${FLEET_REPO_DIR}/${MGMT_PROJECT_NAME}/managed-resources/${MGMT_CLUSTER_NAME}"
896
garciadeblas70461c52024-07-03 09:17:56 +0200897 # Pipeline of transformations to create the cluster resource
898 export CLUSTER_KUSTOMIZATION_NAME
899 folder2list \
900 "${TEMPLATES_DIR}" | \
901 replace_env_vars \
902 '${CLUSTER_KUSTOMIZATION_NAME}' | \
903 patch_replace \
904 ".spec.postBuild.substitute.cluster_name" \
905 "${CLUSTER_NAME}" \
906 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
907 patch_replace \
908 ".spec.postBuild.substitute.vm_size" \
909 "${VM_SIZE}" \
910 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
911 patch_replace \
912 ".spec.postBuild.substitute.node_count" \
913 "${NODE_COUNT}" \
914 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
915 patch_replace \
916 ".spec.postBuild.substitute.cluster_location" \
917 "${CLUSTER_LOCATION}" \
918 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
919 patch_replace \
920 ".spec.postBuild.substitute.k8s_version" \
921 "${K8S_VERSION}" \
922 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
923 patch_replace \
rshri4240a7d2025-06-13 11:30:35 +0000924 ".spec.postBuild.substitute.cluster_iam_role" \
925 "${CLUSTER_IAM_ROLE}" \
926 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
927 patch_replace \
garciadeblas70461c52024-07-03 09:17:56 +0200928 ".spec.postBuild.substitute.providerconfig_name" \
929 "${PROVIDERCONFIG_NAME}" \
930 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
931 transform_if \
932 "${IS_AKS}" \
933 patch_replace \
934 ".spec.postBuild.substitute.rg_name" \
935 "${AKS_RG_NAME}" \
936 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
937 transform_if \
938 "${IS_GKE}" \
939 patch_replace \
940 ".spec.postBuild.substitute.preemptible_nodes" \
941 "${GKE_PREEMPTIBLE_NODES}" \
942 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
rshri4240a7d2025-06-13 11:30:35 +0000943 transform_if \
944 "${PATCH}" \
945 add_patch_to_kustomization_as_list \
946 "${CLUSTER_KUSTOMIZATION_NAME}" \
947 "${PATCH_VALUE}" | \
948 transform_if \
949 "${IS_EKS_AND_IAM}" \
950 add_component_to_kustomization_as_list \
951 "${CLUSTER_KUSTOMIZATION_NAME}" \
952 "${IAM_COMPONENTS[@]}" | \
953 transform_if \
954 "${CONFIG}" \
955 add_config_to_kustomization \
956 "${CLUSTER_KUSTOMIZATION_NAME}" | \
garciadeblas70461c52024-07-03 09:17:56 +0200957 rename_file_in_items \
958 "${TEMPLATE_MANIFEST_FILENAME}" \
959 "${MANIFEST_FILENAME}" | \
rshri4240a7d2025-06-13 11:30:35 +0000960 prepend_folder_path "${CLUSTER_KUSTOMIZATION_NAME}/clusterbase/" | \
garciadeblas70461c52024-07-03 09:17:56 +0200961 list2folder_cp_over \
962 "${TARGET_FOLDER}"
963
964 # Bootstrap (unless asked to skip)
965 if [[ "${SKIP_BOOTSTRAP,,}" == "true" ]]; then
966 return 0
967 fi
968 create_bootstrap_for_remote_cluster \
969 "${CLUSTER_NAME}" \
970 "${CLUSTER_KUSTOMIZATION_NAME}" \
971 "${FLEET_REPO_DIR}" \
972 "${SW_CATALOGS_REPO_DIR}" \
973 "${FLEET_REPO_URL}" \
974 "${SW_CATALOGS_REPO_URL}" \
975 "${MGMT_PROJECT_NAME}" \
976 "${PUBLIC_KEY_MGMT}" \
977 "${PUBLIC_KEY_NEW_CLUSTER}" \
978 "${PRIVATE_KEY_NEW_CLUSTER}"
979}
980
981
982# Delete remote cluster (generic for any cloud)
983function delete_remote_cluster() {
984 local CLUSTER_KUSTOMIZATION_NAME="$1"
985 local PROJECT_NAME="${2:-"${MGMT_PROJECT_NAME}"}"
986 local FLEET_REPO_DIR="${3:-"${FLEET_REPO_DIR}"}"
987 local MGMT_RESOURCES_DIR="${4:-"${MGMT_RESOURCES_DIR}"}"
988
989 # Optional inputs: Paths for each profile in the Git repo
garciadeblas94e638f2025-08-06 17:53:22 +0200990 local INFRA_CONTROLLERS_DIR="${5:-"${FLEET_REPO_DIR}/${PROJECT_NAME}/infra-controller-profiles/${CLUSTER_KUSTOMIZATION_NAME}"}"
991 local INFRA_CONFIGS_DIR="${6:-"${FLEET_REPO_DIR}/${PROJECT_NAME}/infra-config-profiles/${CLUSTER_KUSTOMIZATION_NAME}"}"
992 local MANAGED_RESOURCES_DIR="${7:-"${FLEET_REPO_DIR}/${PROJECT_NAME}/managed-resources/${CLUSTER_KUSTOMIZATION_NAME}"}"
993 local APPS_DIR="${8:-"${FLEET_REPO_DIR}/${PROJECT_NAME}/app-profiles/${CLUSTER_KUSTOMIZATION_NAME}"}"
994 local CLUSTER_DIR="${9:-"${FLEET_REPO_DIR}/clusters/${CLUSTER_KUSTOMIZATION_NAME}"}"
garciadeblas70461c52024-07-03 09:17:56 +0200995
garciadeblasc4afd542025-06-18 17:37:59 +0200996 # Optional input: Do I need a purge operation first?
garciadeblas94e638f2025-08-06 17:53:22 +0200997 local PURGE="${10:-"false"}"
garciadeblasc4afd542025-06-18 17:37:59 +0200998
999
1000 # Perform the purge if needed
1001 if [[ "${PURGE,,}" == "true" ]]; then
1002 echo "Purging the remote Flux instalation..."
garciadeblas94e638f2025-08-06 17:53:22 +02001003 flux uninstall -s --namespace=flux-system
garciadeblasc4afd542025-06-18 17:37:59 +02001004 fi
1005
1006 echo "Deleting cluster profiles and (when applicable) its cloud resources..."
1007
garciadeblas70461c52024-07-03 09:17:56 +02001008 # Delete profile folders
1009 rm -rf "${INFRA_CONTROLLERS_DIR}"
1010 rm -rf "${INFRA_CONFIGS_DIR}"
1011 rm -rf "${MANAGED_RESOURCES_DIR}"
1012 rm -rf "${APPS_DIR}"
1013
1014 # Delete base cluster Kustomizations
1015 rm -rf "${CLUSTER_DIR}"
1016
garciadeblasc4afd542025-06-18 17:37:59 +02001017 # Delete cluster resources if managed by OSM (otherwise, this will be ignored)
garciadeblas70461c52024-07-03 09:17:56 +02001018 rm -rf "${MGMT_RESOURCES_DIR}/${CLUSTER_KUSTOMIZATION_NAME}"
1019}
1020
1021
1022# Update remote CrossPlane cluster (generic for any cloud)
1023function update_crossplane_cluster() {
1024 local CLUSTER_KUSTOMIZATION_NAME="$1"
1025 local CLUSTER_NAME="$2"
1026 # As of today, one among `aks`, `eks` or `gke`:
1027 local CLUSTER_TYPE="$3"
1028 local PROVIDERCONFIG_NAME="${4:-default}"
1029 local VM_SIZE="$5"
1030 local NODE_COUNT="$6"
1031 local CLUSTER_LOCATION="$7"
1032 local K8S_VERSION="${8:-"'1.28'"}"
1033 local PUBLIC_KEY_MGMT="${9:-"${PUBLIC_KEY_MGMT}"}"
1034 local PUBLIC_KEY_NEW_CLUSTER="${10}"
1035 local PRIVATE_KEY_NEW_CLUSTER="${11:-"${PRIVATE_KEY_NEW_CLUSTER}"}"
1036 # AKS only
1037 local AKS_RG_NAME="${12:-""}"
1038 # GKE only
1039 local GKE_PREEMPTIBLE_NODES="${13:-""}"
1040 ## `FLEET_REPO_DIR` is the result of:
1041 ## "{{inputs.parameters.fleet_mount_path}}/{{inputs.parameters.cloned_fleet_folder_name}}"
1042 local FLEET_REPO_DIR="${14:-"${FLEET_REPO_DIR}"}"
1043 local FLEET_REPO_URL="${15:-""}"
1044 ## `SW_CATALOGS_REPO_DIR` is the result of:
1045 ## "{{inputs.parameters.sw_catalogs_mount_path}}/{{inputs.parameters.cloned_sw_catalogs_folder_name}}"
1046 local SW_CATALOGS_REPO_DIR="${16:-"${SW_CATALOGS_REPO_DIR}"}"
1047 local SW_CATALOGS_REPO_URL="${17:-""}"
1048 # Prevent a new bootstrap by default
1049 local SKIP_BOOTSTRAP="${18:"true"}"
1050 # Only change if absolutely needeed
1051 local MGMT_PROJECT_NAME="${19:-"osm_admin"}"
1052 local MGMT_CLUSTER_NAME="${20:-"_management"}"
1053 local BASE_TEMPLATES_PATH="${21:-"cloud-resources"}"
1054 local TEMPLATE_MANIFEST_FILENAME="${22:-"${CLUSTER_TYPE,,}01.yaml"}"
1055 local MANIFEST_FILENAME="${23:-"${CLUSTER_TYPE,,}-${CLUSTER_NAME}.yaml"}"
1056
garciadeblas70461c52024-07-03 09:17:56 +02001057 # Is the provider type supported?
1058 local VALID_PROVIDERS=("eks" "aks" "gke")
1059 CLUSTER_TYPE="${CLUSTER_TYPE,,}"
1060 [[ ! ($(echo ${VALID_PROVIDERS[@]} | grep -w "${CLUSTER_TYPE}")) ]] && return 1
1061
1062 # Determine key folders in Fleet
1063 local MGMT_RESOURCES_DIR="${FLEET_REPO_DIR}/${MGMT_PROJECT_NAME}/managed-resources/${MGMT_CLUSTER_NAME}"
1064
1065 # First, delete cluster's CrossPlane resources
1066 # NOTE: We only delete de Kustomization referring to CrossPlane resources,
1067 # not the bootstrap resources or the profiles. Thus we avoid that KSUs are
1068 # affected or a potential second unnecesary bootstrap.
1069 rm -rf "${MGMT_RESOURCES_DIR}/${CLUSTER_KUSTOMIZATION_NAME}/${MANIFEST_FILENAME}"
1070
1071 # Then, recreate the manifests with updated values
1072 create_crossplane_cluster \
1073 "${CLUSTER_KUSTOMIZATION_NAME}" \
1074 "${CLUSTER_NAME}" \
1075 "${CLUSTER_TYPE}" \
1076 "${PROVIDERCONFIG_NAME}" \
1077 "${VM_SIZE}" \
1078 "${NODE_COUNT}" \
1079 "${CLUSTER_LOCATION}" \
1080 "${K8S_VERSION}" \
1081 "${PUBLIC_KEY_MGMT}" \
1082 "${PUBLIC_KEY_NEW_CLUSTER}" \
1083 "${PRIVATE_KEY_NEW_CLUSTER}" \
1084 "${AKS_RG_NAME}" \
1085 "${GKE_PREEMPTIBLE_NODES}" \
1086 "${FLEET_REPO_DIR}" \
1087 "${FLEET_REPO_URL}" \
1088 "${SW_CATALOGS_REPO_DIR}" \
1089 "${SW_CATALOGS_REPO_URL}" \
1090 "${SKIP_BOOTSTRAP}" \
1091 "${MGMT_PROJECT_NAME}" \
1092 "${MGMT_CLUSTER_NAME}" \
1093 "${BASE_TEMPLATES_PATH}" \
1094 "${TEMPLATE_MANIFEST_FILENAME}" \
1095 "${MANIFEST_FILENAME}"
1096}
1097
garciadeblas94e638f2025-08-06 17:53:22 +02001098
garciadeblas8a28f6d2025-06-11 11:11:56 +02001099# Create remote CAPI cluster for Openstack
1100function create_capi_openstack_cluster() {
1101 local CLUSTER_KUSTOMIZATION_NAME="${1}"
1102 local CLUSTER_NAME="${2}"
1103 local VM_SIZE="${3}"
1104 local VM_SIZE_CONTROL_PLANE="${4:-"${VM_SIZE}"}"
1105 local NODE_COUNT="${5}"
1106 local NODE_COUNT_CONTROLPLANE="${6:-"1"}"
1107 local K8S_VERSION="${7}"
1108 # OpenStack specific
1109 local OPENSTACK_CLOUD_NAME="${8}"
1110 local OPENSTACK_DNS_NAMESERVERS="${9}"
1111 local OPENSTACK_EXTERNAL_NETWORK_ID="${10}"
1112 local OPENSTACK_FAILURE_DOMAIN="${11}"
1113 local OPENSTACK_SSH_KEY_NAME="${12}"
1114 local CNI="${13:-"calico"}"
1115 local OPENSTACK_WORKER_IMAGE_NAME="${14:-"osm-capo-node-${K8S_VERSION}"}"
1116 local OPENSTACK_CONTROL_PLANE_IMAGE_NAME="${15:-"${OPENSTACK_WORKER_IMAGE_NAME}"}"
1117 # SOPS-AGE related
1118 local PUBLIC_KEY_MGMT="${16:-"${PUBLIC_KEY_MGMT}"}"
1119 local PUBLIC_KEY_NEW_CLUSTER="${17:-"${PUBLIC_KEY_NEW_CLUSTER}"}"
1120 local PRIVATE_KEY_NEW_CLUSTER="${18:-"${PRIVATE_KEY_NEW_CLUSTER}"}"
1121 # GitOps retaled
1122 local FLEET_REPO_DIR="${19:-"${FLEET_REPO_DIR}"}"
1123 local FLEET_REPO_URL="${20:-"${FLEET_REPO_URL}"}"
1124 local SW_CATALOGS_REPO_DIR="${21:-"${SW_CATALOGS_REPO_DIR}"}"
1125 local SW_CATALOGS_REPO_URL="${22:-"${SW_CATALOGS_REPO_URL}"}"
1126 local SKIP_BOOTSTRAP="${23:-"false"}"
1127 local MGMT_PROJECT_NAME="${24:-"osm_admin"}"
1128 local MGMT_CLUSTER_NAME="${25:-"_management"}"
1129 local BASE_TEMPLATES_PATH="${26:-"cloud-resources/capi"}"
1130 local NAMESPACE="${27:-"managed-resources"}"
1131
1132 # Varibles with valus from convention.
1133 local CLUSTER_TYPE="openstack"
1134 local TEMPLATE_MANIFEST_FILENAME="capi-cluster.yaml"
1135 local MANIFEST_FILENAME="openstack-${CLUSTER_NAME}.yaml"
1136 local CLOUD_CREDENTIALS="${OPENSTACK_CLOUD_NAME}-capo-config"
1137
1138 # Determines the source dir for the templates and the target folder in Fleet
1139 local TEMPLATES_DIR="${SW_CATALOGS_REPO_DIR}/${BASE_TEMPLATES_PATH}/openstack-kubeadm/templates"
1140 local TARGET_FOLDER="${FLEET_REPO_DIR}/${MGMT_PROJECT_NAME}/managed-resources/${MGMT_CLUSTER_NAME}"
1141 export CNI=${CNI,,}
1142
1143 # Variables for kubeconfig secret reference
1144 export CLUSTER_KUBECONFIG_SECRET_NAME="${CLUSTER_KUSTOMIZATION_NAME}-kubeconfig"
1145 export CLUSTER_KUBECONFIG_SECRET_KEY="value"
1146
1147 export CLUSTER_KUSTOMIZATION_NAME
1148 export OPENSTACK_CLOUD_NAME
1149
1150 folder2list \
1151 "${TEMPLATES_DIR}" | \
1152 replace_env_vars \
1153 '${CLUSTER_KUSTOMIZATION_NAME},${CNI},${CLUSTER_KUBECONFIG_SECRET_NAME},${CLUSTER_KUBECONFIG_SECRET_KEY},${OPENSTACK_CLOUD_NAME}' | \
1154 patch_replace \
1155 ".spec.postBuild.substitute.cluster_name" \
1156 "${CLUSTER_NAME}" \
1157 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
1158 patch_replace \
1159 ".spec.postBuild.substitute.cni" \
1160 "${CNI}" \
1161 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
1162 patch_replace \
1163 ".spec.postBuild.substitute.control_plane_machine_count" \
1164 "${NODE_COUNT_CONTROLPLANE}" \
1165 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
1166 patch_replace \
1167 ".spec.postBuild.substitute.kubernetes_version" \
1168 "v${K8S_VERSION}" \
1169 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
1170 patch_replace \
1171 ".spec.postBuild.substitute.namespace" \
1172 "${NAMESPACE}" \
1173 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
1174 patch_replace \
1175 ".spec.postBuild.substitute.worker_machine_count" \
1176 "${NODE_COUNT}" \
1177 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
1178 patch_replace \
1179 ".spec.postBuild.substitute.openstack_cloud" \
1180 "${OPENSTACK_CLOUD_NAME}" \
1181 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
1182 patch_replace \
1183 ".spec.postBuild.substitute.openstack_cloud_conf" \
1184 "${CLOUD_CREDENTIALS}" \
1185 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
1186 patch_replace \
1187 ".spec.postBuild.substitute.openstack_control_plane_machine_flavor" \
1188 "${VM_SIZE_CONTROL_PLANE}" \
1189 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
1190 patch_replace \
1191 ".spec.postBuild.substitute.openstack_dns_nameservers" \
1192 "${OPENSTACK_DNS_NAMESERVERS}" \
1193 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
1194 patch_replace \
1195 ".spec.postBuild.substitute.openstack_external_network_id" \
1196 "${OPENSTACK_EXTERNAL_NETWORK_ID}" \
1197 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
1198 patch_replace \
1199 ".spec.postBuild.substitute.openstack_failure_domain" \
1200 "${OPENSTACK_FAILURE_DOMAIN}" \
1201 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
1202 patch_replace \
1203 ".spec.postBuild.substitute.openstack_worker_image_name" \
1204 "${OPENSTACK_WORKER_IMAGE_NAME}" \
1205 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
1206 patch_replace \
1207 ".spec.postBuild.substitute.openstack_control_plane_image_name" \
1208 "${OPENSTACK_CONTROL_PLANE_IMAGE_NAME}" \
1209 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
1210 patch_replace \
1211 ".spec.postBuild.substitute.openstack_node_machine_flavor" \
1212 "${VM_SIZE}" \
1213 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
1214 patch_replace \
1215 ".spec.postBuild.substitute.openstack_ssh_key_name" \
1216 "${OPENSTACK_SSH_KEY_NAME}" \
1217 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
1218 rename_file_in_items \
1219 "${TEMPLATE_MANIFEST_FILENAME}" \
1220 "${MANIFEST_FILENAME}" | \
1221 prepend_folder_path "${CLUSTER_KUSTOMIZATION_NAME}/" | \
1222 list2folder_cp_over \
1223 "${TARGET_FOLDER}"
garciadeblas94e638f2025-08-06 17:53:22 +02001224
garciadeblas8a28f6d2025-06-11 11:11:56 +02001225 # Bootstrap (unless asked to skip)
1226 if [[ "${SKIP_BOOTSTRAP,,}" == "true" ]]; then
1227 return 0
1228 fi
garciadeblas94e638f2025-08-06 17:53:22 +02001229
garciadeblas8a28f6d2025-06-11 11:11:56 +02001230 create_bootstrap_for_remote_cluster \
1231 "${CLUSTER_NAME}" \
1232 "${CLUSTER_KUSTOMIZATION_NAME}" \
1233 "${FLEET_REPO_DIR}" \
1234 "${SW_CATALOGS_REPO_DIR}" \
1235 "${FLEET_REPO_URL}" \
1236 "${SW_CATALOGS_REPO_URL}" \
1237 "${MGMT_PROJECT_NAME}" \
1238 "${PUBLIC_KEY_MGMT}" \
1239 "${PUBLIC_KEY_NEW_CLUSTER}" \
1240 "${PRIVATE_KEY_NEW_CLUSTER}" \
1241 "false" \
1242 '' \
1243 "${CLUSTER_KUBECONFIG_SECRET_NAME}" \
1244 "${CLUSTER_KUBECONFIG_SECRET_KEY}"
1245
1246}
1247
1248# Update remote CAPI cluster for Openstack
1249function update_capi_openstack_cluster() {
1250 local CLUSTER_KUSTOMIZATION_NAME="${1}"
1251 local CLUSTER_NAME="${2}"
1252 local VM_SIZE="${3}"
1253 local VM_SIZE_CONTROL_PLANE="${4}"
1254 local NODE_COUNT="${5}"
1255 local NODE_COUNT_CONTROLPLANE="${6}"
1256 local K8S_VERSION="${7}"
1257 # OpenStack specific
1258 local OPENSTACK_CLOUD_NAME="${8}"
1259 local OPENSTACK_DNS_NAMESERVERS="${9}"
1260 local OPENSTACK_EXTERNAL_NETWORK_ID="${10}"
1261 local OPENSTACK_FAILURE_DOMAIN="${11}"
1262 local OPENSTACK_SSH_KEY_NAME="${12}"
1263 local CNI="${13:-"calico"}"
1264 local OPENSTACK_WORKER_IMAGE_NAME="${14:-"osm-capo-node-${K8S_VERSION}"}"
1265 local OPENSTACK_CONTROL_PLANE_IMAGE_NAME="${15:-"${OPENSTACK_WORKER_IMAGE_NAME}"}"
1266 # SOPS-AGE related
1267 local PUBLIC_KEY_MGMT="${16:-"${PUBLIC_KEY_MGMT}"}"
1268 local PUBLIC_KEY_NEW_CLUSTER="${17:-"${PUBLIC_KEY_NEW_CLUSTER}"}"
1269 local PRIVATE_KEY_NEW_CLUSTER="${18:-"${PRIVATE_KEY_NEW_CLUSTER}"}"
1270 # GitOps retaled
1271 local FLEET_REPO_DIR="${19:-"${FLEET_REPO_DIR}"}"
1272 local FLEET_REPO_URL="${20:-"${FLEET_REPO_URL}"}"
1273 local SW_CATALOGS_REPO_DIR="${21:-"${SW_CATALOGS_REPO_DIR}"}"
1274 local SW_CATALOGS_REPO_URL="${22:-"${SW_CATALOGS_REPO_URL}"}"
1275 local MGMT_PROJECT_NAME="${23:-"osm_admin"}"
1276 local MGMT_CLUSTER_NAME="${24:-"_management"}"
1277 local BASE_TEMPLATES_PATH="${25:-"cloud-resources/capi"}"
1278 local NAMESPACE="${26:-"managed-resources"}"
garciadeblas94e638f2025-08-06 17:53:22 +02001279
garciadeblas8a28f6d2025-06-11 11:11:56 +02001280 # Determine key folders in Fleet
1281 local MGMT_RESOURCES_DIR="${FLEET_REPO_DIR}/${MGMT_PROJECT_NAME}/managed-resources/${MGMT_CLUSTER_NAME}"
1282
1283 # Updating no new cluster
1284 local SKIP_BOOTSTRAP="true"
garciadeblas94e638f2025-08-06 17:53:22 +02001285
garciadeblas8a28f6d2025-06-11 11:11:56 +02001286 create_capi_openstack_cluster \
1287 "${CLUSTER_KUSTOMIZATION_NAME}" \
1288 "${CLUSTER_NAME}" \
1289 "${VM_SIZE}" \
1290 "${VM_SIZE_CONTROL_PLANE}" \
1291 "${NODE_COUNT}" \
1292 "${NODE_COUNT_CONTROLPLANE}" \
1293 "${K8S_VERSION}" \
1294 "${OPENSTACK_CLOUD_NAME}" \
1295 "${OPENSTACK_DNS_NAMESERVERS}" \
1296 "${OPENSTACK_EXTERNAL_NETWORK_ID}" \
1297 "${OPENSTACK_FAILURE_DOMAIN}" \
1298 "${OPENSTACK_SSH_KEY_NAME}" \
1299 "${CNI}" \
1300 "${OPENSTACK_WORKER_IMAGE_NAME}" \
1301 "${OPENSTACK_CONTROL_PLANE_IMAGE_NAME}" \
1302 "${PUBLIC_KEY_MGMT}" \
1303 "${PUBLIC_KEY_NEW_CLUSTER}" \
1304 "${PRIVATE_KEY_NEW_CLUSTER}" \
1305 "${FLEET_REPO_DIR}" \
1306 "${FLEET_REPO_URL}" \
1307 "${SW_CATALOGS_REPO_DIR}" \
1308 "${SW_CATALOGS_REPO_URL}" \
1309 "${SKIP_BOOTSTRAP}" \
1310 "${MGMT_PROJECT_NAME}" \
1311 "${MGMT_CLUSTER_NAME}" \
1312 "${BASE_TEMPLATES_PATH}" \
1313 "${NAMESPACE}"
1314}
1315
1316# Create remote Openshift cluster via ACM
1317function create_openshift_cluster {
1318 local CLUSTER_KUSTOMIZATION_NAME="${1}"
1319 local CLUSTER_NAME="${2}"
1320 # This has to be void. Stored in database
1321 local K8S_VERSION="${3:-"''"}"
1322 # SOPS-AGE related
1323 local PUBLIC_KEY_ACM="${4}"
1324 local PUBLIC_KEY_NEW_CLUSTER="${5:-"${PUBLIC_KEY_NEW_CLUSTER}"}"
1325 local PRIVATE_KEY_NEW_CLUSTER="${6:-"${PRIVATE_KEY_NEW_CLUSTER}"}"
1326 # OpenShift
1327 local OPENSHIFT_RELEASE="${7}"
1328 local INFRA_PUBLIC_SSH_KEY="${8}"
1329 local CONTROL_PLANE_AVAILABILITY="${9}"
1330 local WORKER_COUNT="${10}"
1331 local WORKER_CORES="${11}"
1332 local WORKER_MEMORY="${12}"
1333 local WORKER_VOLUME_SIZE="${13}"
1334 local STORAGE_CLASS="${14}"
1335 local BASE_DOMAIN="${15}"
1336 local MGMT_CLUSTER_NAME="${16}"
1337 local HOSTED_CLUSTERS_PROJECT="${17:-"clusters"}"
1338 local ETCD_VOLUME_SIZE="${18:-"8"}"
1339 # GitOps retaled
1340 local FLEET_REPO_DIR="${19:-"${FLEET_REPO_DIR}"}"
1341 local FLEET_REPO_URL="${20:-"${FLEET_REPO_URL}"}"
1342 local SW_CATALOGS_REPO_DIR="${21:-"${SW_CATALOGS_REPO_DIR}"}"
1343 local SW_CATALOGS_REPO_URL="${22:-"${SW_CATALOGS_REPO_URL}"}"
1344 local SKIP_BOOTSTRAP="${23:-"false"}"
1345 # Only change if absolutely needeed
1346 local MGMT_PROJECT_NAME="${24:-"osm_admin"}"
1347 local BASE_TEMPLATES_PATH="${25:-"cloud-resources"}"
1348 local TEMPLATE_MANIFEST_FILENAME="${26:-"openshift01.yaml"}"
1349 local MANIFEST_FILENAME="${27:-"openshift-${CLUSTER_NAME}.yaml"}"
garciadeblas94e638f2025-08-06 17:53:22 +02001350
garciadeblas8a28f6d2025-06-11 11:11:56 +02001351 local TEMPLATES_DIR="${SW_CATALOGS_REPO_DIR}/cloud-resources/openshift/templates"
1352 local TARGET_FOLDER="${FLEET_REPO_DIR}/${MGMT_PROJECT_NAME}/managed-resources/${MGMT_CLUSTER_NAME}"
1353
1354 # Internally ACM creates several projects for each cluster.
1355 # Specifically the klusterletaddonconfig must land in a project with the same name as the cluster.
1356 # This will be specifically controlled by the variable `CLUSTER_PROJECT`.
1357 #
1358 # It must be notes that CLUSTER_NAME, CLUSTER_KUSTOMIZATION_NAME and CLUSTER_PROJECT have the same value,
1359 # but they are conceptually different.
1360 local CLUSTER_PROJECT="${CLUSTER_KUSTOMIZATION_NAME}"
1361
1362 export CLUSTER_KUSTOMIZATION_NAME
1363
1364 folder2list \
1365 "${TEMPLATES_DIR}" | \
1366 replace_env_vars \
1367 '${CLUSTER_KUSTOMIZATION_NAME}' | \
1368 patch_replace \
1369 ".spec.postBuild.substitute.base_domain" \
1370 "${BASE_DOMAIN}" \
1371 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
1372 patch_replace \
1373 ".spec.postBuild.substitute.cluster_name" \
1374 "${CLUSTER_NAME}" \
1375 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
1376 patch_replace \
1377 ".spec.postBuild.substitute.cluster_project" \
1378 "${CLUSTER_PROJECT}" \
1379 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
1380 patch_replace \
1381 ".spec.postBuild.substitute.hosted_cluster_project" \
1382 "${HOSTED_CLUSTERS_PROJECT}" \
1383 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
1384 patch_replace \
1385 ".spec.postBuild.substitute.etcd_volume_size" \
1386 "${ETCD_VOLUME_SIZE}Gi" \
1387 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
1388 patch_replace \
1389 ".spec.postBuild.substitute.openshift_release" \
1390 "${OPENSHIFT_RELEASE}" \
1391 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
1392 patch_replace \
1393 ".spec.postBuild.substitute.storage_class" \
1394 "${STORAGE_CLASS}" \
1395 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
1396 patch_replace \
1397 ".spec.postBuild.substitute.control_plane_availability" \
1398 "${CONTROL_PLANE_AVAILABILITY}" \
1399 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
1400 patch_replace \
1401 ".spec.postBuild.substitute.worker_count" \
1402 "${WORKER_COUNT}" \
1403 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
1404 patch_replace \
1405 ".spec.postBuild.substitute.worker_cores" \
1406 "${WORKER_CORES}" \
1407 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
1408 patch_replace \
1409 ".spec.postBuild.substitute.worker_memory" \
1410 "${WORKER_MEMORY}Gi" \
1411 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
1412 patch_replace \
1413 ".spec.postBuild.substitute.worker_volume_size" \
1414 "${WORKER_VOLUME_SIZE}Gi" \
1415 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}\")" | \
1416 patch_replace \
1417 ".spec.postBuild.substitute.cluster_project" \
1418 "${CLUSTER_PROJECT}" \
1419 "| select(.kind == \"Kustomization\") | select(.metadata.name == \"${CLUSTER_KUSTOMIZATION_NAME}-ns\")" | \
1420 rename_file_in_items \
1421 "${TEMPLATE_MANIFEST_FILENAME}" \
1422 "${MANIFEST_FILENAME}" | \
1423 prepend_folder_path "${CLUSTER_KUSTOMIZATION_NAME}/" | \
1424 list2folder_cp_over \
1425 "${TARGET_FOLDER}"
1426
1427 echo "" | \
1428 make_generator \
1429 "pull-secret.yaml" \
1430 kubectl_encrypt \
1431 "${PUBLIC_KEY_ACM}" \
1432 create \
1433 secret \
1434 generic \
1435 "pullsecret-cluster-${CLUSTER_NAME}" \
1436 --namespace="${HOSTED_CLUSTERS_PROJECT}" \
1437 --from-file=".dockerconfigjson"=<(echo "${DOCKERCONFIGJSON}") \
1438 -o=yaml \
1439 --dry-run=client | \
1440 make_generator \
1441 "ssh-key-secret.yaml" \
1442 kubectl_encrypt \
1443 "${PUBLIC_KEY_ACM}" \
1444 create \
1445 secret \
1446 generic \
1447 "sshkey-cluster-${CLUSTER_NAME}" \
1448 --namespace="${HOSTED_CLUSTERS_PROJECT}" \
1449 --from-file='id_rsa.pub'=<(echo "${INFRA_PUBLIC_SSH_KEY}") \
1450 -o=yaml \
1451 --dry-run=client | \
1452 list2folder_cp_over \
1453 "${TARGET_FOLDER}/${CLUSTER_KUSTOMIZATION_NAME}"
1454
1455 # Bootstrap (unless asked to skip)
1456 if [[ "${SKIP_BOOTSTRAP,,}" == "true" ]]; then
1457 return 0
1458 fi
1459
1460 local CLUSTER_KUBECONFIG_SECRET_NAME="${CLUSTER_KUSTOMIZATION_NAME}-admin-kubeconfig"
1461 local CLUSTER_KUBECONFIG_SECRET_KEY="kubeconfig"
1462 local BOOTSTRAP_KUSTOMIZATION_NAMESPACE="${HOSTED_CLUSTERS_PROJECT}"
1463 local CLUSTER_KUSTOMIZATION_NAMESPACE="managed-resources"
1464 local BOOTSTRAP_SECRET_NAMESPACE="managed-resources"
1465
1466 create_bootstrap_for_remote_cluster \
1467 "${CLUSTER_NAME}" \
1468 "${CLUSTER_KUSTOMIZATION_NAME}" \
1469 "${FLEET_REPO_DIR}" \
1470 "${SW_CATALOGS_REPO_DIR}" \
1471 "${FLEET_REPO_URL}" \
1472 "${SW_CATALOGS_REPO_URL}" \
1473 "${MGMT_PROJECT_NAME}" \
1474 "${PUBLIC_KEY_ACM}" \
1475 "${PUBLIC_KEY_NEW_CLUSTER}" \
1476 "${PRIVATE_KEY_NEW_CLUSTER}" \
1477 "false" \
1478 "${MGMT_CLUSTER_NAME}" \
1479 "${CLUSTER_KUBECONFIG_SECRET_NAME}" \
1480 "${CLUSTER_KUBECONFIG_SECRET_KEY}" \
1481 "${SW_CATALOGS_REPO_DIR}/cloud-resources/flux-remote-bootstrap/cluster-base-openshift/templates" \
1482 "${BOOTSTRAP_KUSTOMIZATION_NAMESPACE}" \
1483 "${CLUSTER_KUSTOMIZATION_NAMESPACE}" \
1484 "${BOOTSTRAP_SECRET_NAMESPACE}"
1485
1486}
1487
1488# Update remote Openshift cluster via ACM
1489function update_openshift_cluster {
1490 local CLUSTER_KUSTOMIZATION_NAME="${1}"
1491 local CLUSTER_NAME="${2}"
1492 # This has to be void. Stored in database
1493 local K8S_VERSION="${3:-"''"}"
1494 # SOPS-AGE related
1495 local PUBLIC_KEY_ACM="${4}"
1496 local PUBLIC_KEY_NEW_CLUSTER="${5:-"${PUBLIC_KEY_NEW_CLUSTER}"}"
1497 local PRIVATE_KEY_NEW_CLUSTER="${6:-"${PRIVATE_KEY_NEW_CLUSTER}"}"
1498 # OpenShift specific
1499 local OPENSHIFT_RELEASE="${7}"
1500 local INFRA_PUBLIC_SSH_KEY="${8}"
1501 local CONTROL_PLANE_AVAILABILITY="${9}"
1502 local WORKER_COUNT="${10}"
1503 local WORKER_CORES="${11}"
1504 local WORKER_MEMORY="${12}"
1505 local WORKER_VOLUME_SIZE="${13}"
1506 local STORAGE_CLASS="${14}"
1507 local BASE_DOMAIN="${15}"
1508 local MGMT_CLUSTER_NAME="${16}"
1509 local HOSTED_CLUSTERS_PROJECT="${17:-"clusters"}"
1510 local ETCD_VOLUME_SIZE="${18:-"8"}"
1511 # GitOps retaled
1512 local FLEET_REPO_DIR="${19:-"${FLEET_REPO_DIR}"}"
1513 local FLEET_REPO_URL="${20:-"${FLEET_REPO_URL}"}"
1514 local SW_CATALOGS_REPO_DIR="${21:-"${SW_CATALOGS_REPO_DIR}"}"
1515 local SW_CATALOGS_REPO_URL="${22:-"${SW_CATALOGS_REPO_URL}"}"
1516 local SKIP_BOOTSTRAP="${23:-"false"}"
1517 # Only change if absolutely needeed
1518 local MGMT_PROJECT_NAME="${24:-"osm_admin"}"
garciadeblas94e638f2025-08-06 17:53:22 +02001519
garciadeblas8a28f6d2025-06-11 11:11:56 +02001520 # Determine key folders in Fleet
1521 local MGMT_RESOURCES_DIR="${FLEET_REPO_DIR}/${MGMT_PROJECT_NAME}/managed-resources/${MGMT_CLUSTER_NAME}"
1522
1523 # Updating no new cluster
1524 local SKIP_BOOTSTRAP="true"
1525
1526 create_openshift_cluster \
1527 "${CLUSTER_KUSTOMIZATION_NAME}" \
1528 "${CLUSTER_NAME}" \
1529 "${K8S_VERSION}" \
1530 "${PUBLIC_KEY_ACM}" \
1531 "${PUBLIC_KEY_NEW_CLUSTER}" \
1532 "${PRIVATE_KEY_NEW_CLUSTER}" \
1533 "${OPENSHIFT_RELEASE}" \
1534 "${INFRA_PUBLIC_SSH_KEY}" \
1535 "${CONTROL_PLANE_AVAILABILITY}" \
1536 "${WORKER_COUNT}" \
1537 "${WORKER_CORES}" \
1538 "${WORKER_MEMORY}" \
1539 "${WORKER_VOLUME_SIZE}" \
1540 "${STORAGE_CLASS}" \
1541 "${BASE_DOMAIN}" \
1542 "${MGMT_CLUSTER_NAME}" \
1543 "${HOSTED_CLUSTERS_PROJECT}" \
1544 "${ETCD_VOLUME_SIZE}" \
1545 "${FLEET_REPO_DIR}" \
1546 "${FLEET_REPO_URL}" \
1547 "${SW_CATALOGS_REPO_DIR}" \
1548 "${SW_CATALOGS_REPO_URL}" \
1549 "${SKIP_BOOTSTRAP}" \
1550 "${MGMT_PROJECT_NAME}"
1551}
garciadeblas70461c52024-07-03 09:17:56 +02001552
garciadeblas94e638f2025-08-06 17:53:22 +02001553
garciadeblas70461c52024-07-03 09:17:56 +02001554# ----- Helper functions for adding/removing a profile from a cluster -----
1555
1556# Helper function to find profiles of a given type already used in the cluster
1557function profiles_of_type_in_cluster() {
1558 local CLUSTER_KUSTOMIZATION_NAME="$1"
1559 local RELEVANT_PROFILE_TYPE="$2"
1560 local FLEET_REPO_DIR="${3:-"${FLEET_REPO_DIR}"}"
1561
1562 # Calculated fields
1563 local CLUSTER_FOLDER="${FLEET_REPO_DIR}/clusters/${CLUSTER_KUSTOMIZATION_NAME}"
1564
1565 # Processing (echoes the list)
1566 folder2list \
1567 "${CLUSTER_FOLDER}" | \
1568 get_value_from_resourcelist \
1569 ".metadata.name" \
1570 "| select(.kind == \"Kustomization\")
1571 | select(.metadata.labels.osm_profile_type == \"${RELEVANT_PROFILE_TYPE}\")" | \
1572 multiline2commalist
1573}
1574
1575
1576# Function to list the profiles **this profile depends on**
1577function profiles_this_one_depends_on() {
1578 local CLUSTER_KUSTOMIZATION_NAME="$1"
1579 local PROFILE_TYPE="$2"
1580 local FLEET_REPO_DIR="${3:-"${FLEET_REPO_DIR}"}"
1581
1582 case "${PROFILE_TYPE,,}" in
1583
1584 "controller" | "infra-controller" | "infra-controllers" | "infra_controller" | "infra_controllers")
1585 # Controllers do not depend on any other type of profiles
1586 echo ""
1587 return 0
1588 ;;
1589
1590 "config" | "infra-config" | "infra-configs" | "infra_config" | "infra_configs")
1591 # Infra configs depend on controllers
1592 profiles_of_type_in_cluster \
1593 "${CLUSTER_KUSTOMIZATION_NAME}" \
1594 "infra-controllers" \
1595 "${FLEET_REPO_DIR}"
1596 return 0
1597 ;;
1598
1599 "managed" | "resources" | "managed-resources" | "managed_resources")
1600 # Managed resources depend on infra configs
1601 profiles_of_type_in_cluster \
1602 "${CLUSTER_KUSTOMIZATION_NAME}" \
1603 "infra-configs" \
1604 "${FLEET_REPO_DIR}"
1605 return 0
1606 ;;
1607
1608 "app" |"apps" | "applications" | "cnf" | "cnfs" | "nf" | "nfs")
1609 # Apps (also) depend on infra configs
1610 profiles_of_type_in_cluster \
1611 "${CLUSTER_KUSTOMIZATION_NAME}" \
1612 "infra-configs" \
1613 "${FLEET_REPO_DIR}"
1614 return 0
1615 ;;
1616
1617 *)
1618 echo -n "------------ ERROR ------------"
1619 return 1
1620 ;;
1621 esac
1622}
1623
1624
1625# Function to list the profiles that **depend on this profile**
1626function profiles_depend_on_this_one() {
1627 local CLUSTER_KUSTOMIZATION_NAME="$1"
1628 local PROFILE_TYPE="$2"
1629 local FLEET_REPO_DIR="${3:-"${FLEET_REPO_DIR}"}"
1630
1631 case "${PROFILE_TYPE,,}" in
1632
1633 "controller" | "infra-controller" | "infra-controllers" | "infra_controller" | "infra_controllers")
1634 # Infra configs depend on infra controllers
1635 profiles_of_type_in_cluster \
1636 "${CLUSTER_KUSTOMIZATION_NAME}" \
1637 "infra-configs" \
1638 "${FLEET_REPO_DIR}"
1639 return 0
1640 ;;
1641
1642 "config" | "infra-config" | "infra-configs" | "infra_config" | "infra_configs")
1643 # Both managed resources and apps depend on configs
1644 local PROFILES=(
1645 $(
1646 profiles_of_type_in_cluster \
1647 "${CLUSTER_KUSTOMIZATION_NAME}" \
1648 "managed-resources" \
1649 "${FLEET_REPO_DIR}"
1650 ) \
1651 $(
1652 profiles_of_type_in_cluster \
1653 "${CLUSTER_KUSTOMIZATION_NAME}" \
1654 "apps" \
1655 "${FLEET_REPO_DIR}"
1656 )
1657 )
1658 printf '%s,' "${PROFILES[@]}" | sed 's/,$//g'
1659 return 0
1660 ;;
1661
1662 "managed" | "resources" | "managed-resources" | "managed_resources")
1663 # No other profiles depend on managed resources
1664 echo ""
1665 return 0
1666 ;;
1667
1668 "app" |"apps" | "applications" | "cnf" | "cnfs" | "nf" | "nfs")
1669 # No other profiles depend on apps
1670 echo ""
1671 return 0
1672 ;;
1673
1674 *)
1675 echo -n "------------ ERROR ------------"
1676 return 1
1677 ;;
1678 esac
1679}
1680
1681
1682# Helper function to add a dependency to a Kustomization only if it does not exist already
1683function add_dependency_to_kustomization_safely() {
1684 local KUSTOMIZATION_NAME="$1"
1685 local KUSTOMIZATION_TO_ADD_AS_DEP="$2"
1686
1687 local INPUT=$(cat)
1688 local FILTER="| select(.kind == \"Kustomization\") | select(.metadata.name == \"${KUSTOMIZATION_NAME}\")"
1689
1690 # Check if the dependency was added already
1691 local TEST_RESULT=$(
1692 echo "${INPUT}" | \
1693 is_element_on_list \
1694 ".spec.dependsOn[].name" \
1695 "${KUSTOMIZATION_TO_ADD_AS_DEP}" \
1696 "${FILTER}"
1697 )
1698
1699 # If it existed already, returns the stream as is
1700 if [[ "${TEST_RESULT}" == "true" ]]
1701 then
1702 echo "${INPUT}"
1703 # Otherwise, processes the stream to add it
1704 else
1705 echo "${INPUT}" | \
1706 patch_add_to_list \
1707 ".spec.dependsOn" \
1708 "{name: ${KUSTOMIZATION_TO_ADD_AS_DEP}}" \
1709 "${FILTER}"
1710 fi
1711}
1712
1713
1714# Helper function to remove a dependency from a Kustomization
1715function remove_dependency_from_kustomization_safely() {
1716 local KUSTOMIZATION_NAME="$1"
1717 local KUSTOMIZATION_TO_REMOVE_AS_DEP="$2"
1718
1719 # Calculated inputs
1720 local KEY_PATH=".spec.dependsOn[] | select(.name == \"${KUSTOMIZATION_TO_REMOVE_AS_DEP}\")"
1721 local FILTER="| select(.kind == \"Kustomization\") | select(.metadata.name == \"${KUSTOMIZATION_NAME}\")"
1722
1723 # Remove the entry from the dependency list (if it exists)
1724 yq "del((.items[]${FILTER})${KEY_PATH})"
1725}
1726
1727
1728# Ensure list of Kustomizations depend on a given Kustomization
1729function add_dependency_to_set_of_kustomizations_safely() {
1730 local KS_NAME="$1"
1731 local THEY_DEPEND_ON_THIS="$2"
1732
1733 local INPUT="$(cat)"
1734 local OUTPUT=""
1735
1736 # For each of the Kustomizations on the comma-separated list, adds `KS_NAME` as one of their dependencies
1737 for KUST in ${THEY_DEPEND_ON_THIS//,/ }
1738 do
1739 local OUTPUT="$(
1740 echo "${INPUT}" | \
1741 add_dependency_to_kustomization_safely \
1742 "${KUST}" \
1743 "${KS_NAME}"
1744 )"
1745 local INPUT="${OUTPUT}"
1746 done
1747
1748 # Return the final `ResultList`, after all iterations
1749 echo "${OUTPUT}"
1750}
1751
1752
1753# Ensure list of Kustomizations no longer depend on a given Kustomization
1754function remove_dependency_from_set_of_kustomizations_safely() {
1755 local KS_NAME="$1"
1756 local THEY_NO_LONGER_DEPEND_ON_THIS="$2"
1757
1758 local INPUT="$(cat)"
1759 local OUTPUT=""
1760
1761 # For each of the Kustomizations on the comma-separated list, removes `KS_NAME` from their dependencies
1762 for KUST in ${THEY_NO_LONGER_DEPEND_ON_THIS//,/ }
1763 do
1764 local OUTPUT="$(
1765 echo "${INPUT}" | \
1766 remove_dependency_from_kustomization_safely \
1767 "${KUST}" \
1768 "${KS_NAME}"
1769 )"
1770 local INPUT="${OUTPUT}"
1771 done
1772
1773 # Return the final `ResultList`, after all iterations
1774 echo "${OUTPUT}"
1775}
1776
1777# ----- END of Helper functions for adding/removing a profile from a cluster -----
1778
1779
1780# Add an existing profile to a cluster
1781function attach_profile_to_cluster() {
1782 local PROFILE_NAME="$1"
1783 local PROFILE_TYPE="$2"
1784 local PROJECT_NAME="$3"
1785 local CLUSTER_KUSTOMIZATION_NAME="$4"
1786 local FLEET_REPO_DIR="${5:-"${FLEET_REPO_DIR}"}"
1787
1788 # Calculated inputs
1789 local CLUSTER_FOLDER="${FLEET_REPO_DIR}/clusters/${CLUSTER_KUSTOMIZATION_NAME}"
1790 local TARGET_PROFILE_PATH="$(
1791 path_to_profile \
1792 "${PROFILE_NAME}" \
1793 "${PROFILE_TYPE}" \
1794 "${PROJECT_NAME}"
1795 )"
1796
1797 # Finds out which profiles it should depend on... and which profiles should depend on it
1798 local DEPENDS_ON=$(
1799 profiles_this_one_depends_on \
1800 "${CLUSTER_KUSTOMIZATION_NAME}" \
1801 "${PROFILE_TYPE}" \
1802 "${FLEET_REPO_DIR}"
1803 )
1804
1805 local THEY_DEPEND_ON_THIS=$(
1806 profiles_depend_on_this_one \
1807 "${CLUSTER_KUSTOMIZATION_NAME}" \
1808 "${PROFILE_TYPE}" \
1809 "${FLEET_REPO_DIR}"
1810 )
1811
1812 # Parameters for the new Kustomization object to point to the profile
1813 local KS_NAME="$(safe_name "${PROFILE_TYPE}-${PROFILE_NAME}")"
1814 local MANIFEST_FILENAME="${KS_NAME}.yaml"
1815 local KS_NS=flux-system
1816 local MANIFESTS_PATH="${TARGET_PROFILE_PATH}"
1817 local SOURCE_REPO=GitRepository/fleet-repo.flux-system
1818 local SOURCE_SYNC_INTERVAL="60m"
1819 local HEALTH_CHECK_TO="3m"
1820 local RETRY_INTERVAL="1m"
1821 local TIMEOUT="5m"
1822 local OPTIONS="\
1823 --decryption-provider=sops \
1824 --decryption-secret=sops-age \
1825 --prune=true \
1826 --timeout="${TIMEOUT}" \
1827 --retry-interval="${RETRY_INTERVAL}" \
1828 --label osm_profile_type="${PROFILE_TYPE}"
1829 "
1830
1831 # Finally, we update the folder with all the required changes:
1832 # - Update pre-existing Kustomizations that should depend on the new profile (besides others).
1833 # - Create a new Kustomization pointing to the profile.
1834 # - Update Kustomize's `kustomization.yaml` at the root of the cluster folder to take into account the new Kustomization pointing to the profile.
1835 # - Update the cluster folder accordingly.
1836 folder2list \
1837 "${CLUSTER_FOLDER}" |
1838 add_dependency_to_set_of_kustomizations_safely \
1839 "${KS_NAME}" \
1840 "${THEY_DEPEND_ON_THIS}" | \
1841 generator_kustomization \
1842 "${MANIFEST_FILENAME}" \
1843 "${KS_NAME}" \
1844 "${KS_NS}" \
1845 "${SOURCE_REPO}" \
1846 "${MANIFESTS_PATH}" \
1847 "${SOURCE_SYNC_INTERVAL}" \
1848 "${HEALTH_CHECK_TO}" \
1849 "${DEPENDS_ON}" \
1850 "${OPTIONS}" | \
1851 patch_add_to_list \
1852 ".resources" \
1853 "${MANIFEST_FILENAME}" \
1854 "| select(.kind == \"Kustomization\") | select(.apiVersion == \"kustomize.config.k8s.io/v1beta1\") | select(.metadata.annotations.\"config.kubernetes.io/path\" == \"kustomization.yaml\")" | \
1855 list2folder_sync_replace \
1856 "${CLUSTER_FOLDER}"
1857}
1858
1859
1860# Remove an existing profile from a cluster
1861function detach_profile_from_cluster() {
1862 local PROFILE_NAME="$1"
1863 local PROFILE_TYPE="$2"
1864 local PROJECT_NAME="$3"
1865 local CLUSTER_KUSTOMIZATION_NAME="$4"
1866 local FLEET_REPO_DIR="${5:-"${FLEET_REPO_DIR}"}"
1867
1868 # Calculated inputs
1869 local CLUSTER_FOLDER="${FLEET_REPO_DIR}/clusters/${CLUSTER_KUSTOMIZATION_NAME}"
1870 local TARGET_PROFILE_PATH="$(
1871 path_to_profile \
1872 "${PROFILE_NAME}" \
1873 "${PROFILE_TYPE}" \
1874 "${PROJECT_NAME}"
1875 )"
1876
1877 # Finds out which profiles still depend on it
1878 local THEY_DEPEND_ON_THIS=$(
1879 profiles_depend_on_this_one \
1880 "${CLUSTER_KUSTOMIZATION_NAME}" \
1881 "${PROFILE_TYPE}" \
1882 "${FLEET_REPO_DIR}"
1883 )
1884
1885 # Parameters for the new Kustomization object to point to the profile
1886 local KS_NAME="$(safe_name "${PROFILE_TYPE}-${PROFILE_NAME}")"
1887
1888 # Finally, we update the folder with all the required changes:
1889 # - Update pre-existing Kustomizations that should depend on the new profile (besides others).
1890 # - Create a new Kustomization pointing to the profile.
1891 # - 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.
1892 # - Update the cluster folder accordingly.
1893 folder2list \
1894 "${CLUSTER_FOLDER}" |
1895 remove_dependency_from_set_of_kustomizations_safely \
1896 "${KS_NAME}" \
1897 "${THEY_DEPEND_ON_THIS}" | \
1898 delete_object \
1899 "${KS_NAME}" \
1900 "Kustomization" \
1901 "kustomize.toolkit.fluxcd.io/v1" | \
1902 patch_delete_from_list \
1903 ".resources[] | select(. == \"${MANIFEST_FILENAME}\") " \
1904 "| select(.kind == \"Kustomization\") | select(.apiVersion == \"kustomize.config.k8s.io/v1beta1\") | select(.metadata.annotations.\"config.kubernetes.io/path\" == \"kustomization.yaml\")" | \
1905 list2folder_sync_replace \
1906 "${CLUSTER_FOLDER}"
1907}
1908
1909
1910# Low-level function to add a KSU into a profile
1911function create_ksu_into_profile() {
1912 local KSU_NAME="$1"
1913 local TARGET_PROFILE_FOLDER="$2"
1914 local TEMPLATES_PATH="$3"
1915 local SW_CATALOGS_REPO_DIR="$4"
1916 local TRANSFORMER="${5:-noop_transformer}"
1917
1918 # Gathers all optional parameters for transformer funcion (if any) and puts them into an array for further use
1919 local ALL_PARAMS=( "${@}" )
1920 local TRANSFORMER_ARGS=( "${ALL_PARAMS[@]:5}" )
1921
1922 # Composes the route to the local templates folder
1923 local TEMPLATES_FOLDER="${SW_CATALOGS_REPO_DIR}/${TEMPLATES_PATH}"
1924
1925 folder2list \
1926 "${TEMPLATES_FOLDER}" | \
1927 "${TRANSFORMER}" \
1928 "${TRANSFORMER_ARGS[@]}" | \
1929 prepend_folder_path "${KSU_NAME}/" | \
1930 list2folder_cp_over \
1931 "${TARGET_PROFILE_FOLDER}"
1932}
1933
1934
1935# Function to render a KSU from a `ResourceList` into a profile
1936function render_ksu_into_profile() {
1937 local KSU_NAME="$1"
1938 local PROFILE_NAME="$2"
1939 local PROFILE_TYPE="$3"
1940 local PROJECT_NAME="${4:-"${MGMT_PROJECT_NAME}"}"
1941 local FLEET_REPO_DIR="$5"
1942 local SYNC="${6:-"false"}"
1943
1944 local TARGET_PROFILE_PATH=$(
1945 path_to_profile \
1946 "${PROFILE_NAME}" \
1947 "${PROFILE_TYPE}" \
1948 "${PROJECT_NAME}"
1949 )
1950
1951 local TARGET_PROFILE_FOLDER="${FLEET_REPO_DIR}/${TARGET_PROFILE_PATH}"
1952
1953 # Determines the appropriate function depending on rendering strategy
1954 # - Sync (and potentially delete files in target folder)
1955 # - Copy over (only overwrite changed files, keep the rest)
1956 RENDERER=""
1957 if [[ ${SYNC,,} == "true" ]];
1958 then
1959 RENDERER="list2folder_sync_replace"
1960 else
1961 RENDERER="list2folder_cp_over"
1962 fi
1963
1964 # Render with the selected strategy
1965 [[ "${DRY_RUN,,}" != "true" ]] && mkdir -p "${TARGET_PROFILE_FOLDER}/${KSU_NAME}"
1966 "${RENDERER}" \
1967 "${TARGET_PROFILE_FOLDER}/${KSU_NAME}"
1968 ## This is improves the behaviour of the following code,
1969 ## since avoids unintented deletions in parent folder due to sync
1970 # prepend_folder_path "${KSU_NAME}/" | \
1971 # "${RENDERER}" \
1972 # "${TARGET_PROFILE_FOLDER}"
1973}
1974
1975
1976# High-level function to add a KSU into a profile for the case where
1977# 1. It is originated from an OKA, and
1978# 2. It is based on a HelmRelease.
1979function create_hr_ksu_into_profile() {
1980 # Base KSU generation from template
1981 ## `TEMPLATES_DIR` is the result of:
1982 ## "{{inputs.parameters.sw_catalogs_mount_path}}/{{inputs.parameters.cloned_sw_catalogs_folder_name}}/{{inputs.parameters.templates_path}}"
1983 local TEMPLATES_DIR="$1"
1984 local SUBSTITUTE_ENVIRONMENT="${2:-"false"}"
1985 local SUBSTITUTION_FILTER="${3:-""}"
1986 local CUSTOM_ENV_VARS="${4:-""}"
1987 # Patch HelmRelease in KSU with inline values
1988 local KUSTOMIZATION_NAME="$5"
1989 local HELMRELEASE_NAME="$6"
1990 local INLINE_VALUES="${7:-""}"
1991 # Secret reference and generation (if required)
1992 local IS_PREEXISTING_SECRET="${8:-"false"}"
1993 local TARGET_NS="$9"
1994 local VALUES_SECRET_NAME="${10}"
1995 local SECRET_KEY="${11:-"values.yaml"}"
1996 local AGE_PUBLIC_KEY="${12}"
1997 ## `SECRET_VALUES` will be obtained from the
1998 ## secret named after the input parameter `reference_secret_for_values`,
1999 ## and from the key named after the input parameter `reference_key_for_values`
2000 local LOCAL_SECRET_VALUES="${13:-"${SECRET_VALUES}"}"
2001 # ConfigMap reference and generation (if required)
2002 local IS_PREEXISTING_CM="${14:-"false"}"
2003 local VALUES_CM_NAME="${15:-""}"
2004 local CM_KEY="${16:-""}"
2005 local CM_VALUES="${17:-""}"
2006 # KSU rendering
2007 local KSU_NAME="${18}"
2008 local PROFILE_NAME="${19}"
2009 local PROFILE_TYPE="${20}"
2010 local PROJECT_NAME="${21:-"osm_admin"}"
2011 ## `FLEET_REPO_DIR` is the result of:
2012 ## "{{inputs.parameters.fleet_mount_path}}/{{inputs.parameters.cloned_fleet_folder_name}}"
2013 local FLEET_REPO_DIR="${22:-"/fleet/fleet-osm/"}"
2014 local SYNC="${23:-"true"}"
2015
2016 # Decides which steps may be skipped
2017 HAS_INLINE_VALUES=$([[ -n "${INLINE_VALUES}" ]]; echo $?)
2018 HAS_REFERENCES=$([[ ( -n "${VALUES_SECRET_NAME}" ) || ( -n "${VALUES_CM_NAME}" ) ]]; echo $?)
2019 NEEDS_NEW_SECRET=$([[ ( -n "${VALUES_SECRET_NAME}" ) && ( "${IS_PREEXISTING_SECRET,,}" == "false" ) ]]; echo $?)
2020 NEEDS_NEW_CM=$([[ ( -n "${VALUES_CM_NAME}" ) && ( "${IS_PREEXISTING_CM,,}" == "false" ) ]]; echo $?)
2021 ECHO_RESOURCELIST=$([[ "${DEBUG,,}" == "true" ]]; echo $?)
2022
2023 # If applicable, loads additional environment variables
2024 if [[ -n "${CUSTOM_ENV_VARS}" ]];
2025 then
2026 set -a
2027 source <(echo "${CUSTOM_ENV_VARS}")
2028 set +a
2029 fi
2030
2031 # Runs workflow
2032 folder2list_generator \
2033 "${TEMPLATES_DIR}" \
2034 "${SUBSTITUTE_ENVIRONMENT}" \
2035 "${SUBSTITUTION_FILTER}" | \
2036 transform_if \
2037 "${HAS_INLINE_VALUES}" \
2038 add_values_to_helmrelease_via_ks \
2039 "${KUSTOMIZATION_NAME}" \
2040 "${HELMRELEASE_NAME}" \
2041 "${INLINE_VALUES}" | \
2042 transform_if \
2043 "${HAS_REFERENCES}" \
2044 add_ref_values_to_hr_via_ks \
2045 "${KUSTOMIZATION_NAME}" \
2046 "${HELMRELEASE_NAME}" \
2047 "${VALUES_SECRET_NAME}" \
2048 "${VALUES_CM_NAME}" | \
2049 transform_if \
2050 "${NEEDS_NEW_SECRET}" \
2051 make_generator \
2052 "hr-values-secret.yaml" \
2053 kubectl_encrypt \
2054 "${AGE_PUBLIC_KEY}" \
2055 create \
2056 secret \
2057 generic \
2058 "${VALUES_SECRET_NAME}" \
2059 --namespace="${TARGET_NS}" \
2060 --from-file="${SECRET_KEY}"=<(echo "${LOCAL_SECRET_VALUES}") \
2061 -o=yaml \
2062 --dry-run=client | \
2063 transform_if \
2064 "${NEEDS_NEW_CM}" \
2065 make_generator \
2066 "hr-values-configmap.yaml" \
2067 kubectl \
2068 create \
2069 configmap \
2070 "${VALUES_CM_NAME}" \
2071 --namespace="${TARGET_NS}" \
2072 --from-file="${SECRET_KEY}"=<(echo "${CM_VALUES}") \
2073 -o=yaml \
2074 --dry-run=client | \
2075 transform_if \
2076 "${ECHO_RESOURCELIST}" \
2077 tee /dev/stderr | \
2078 render_ksu_into_profile \
2079 "${KSU_NAME}" \
2080 "${PROFILE_NAME}" \
2081 "${PROFILE_TYPE}" \
2082 "${PROJECT_NAME}" \
2083 "${FLEET_REPO_DIR}" \
2084 "${SYNC}"
2085}
2086
2087
2088# High-level function to update a KSU for the case where
2089# 1. It is originated from an OKA, and
2090# 2. It is based on a HelmRelease.
2091# NOTE: It is an alias of `create_hr_ksu_into_profile`, setting `sync` to true
2092function update_hr_ksu_into_profile() {
2093 # Base KSU generation from template
2094 ## `TEMPLATES_DIR` is the result of:
2095 ## "{{inputs.parameters.sw_catalogs_mount_path}}/{{inputs.parameters.cloned_sw_catalogs_folder_name}}/{{inputs.parameters.templates_path}}"
2096 local TEMPLATES_DIR="$1"
2097 local SUBSTITUTE_ENVIRONMENT="${2:-"false"}"
2098 local SUBSTITUTION_FILTER="${3:-""}"
2099 local CUSTOM_ENV_VARS="${4:-""}"
2100 # Patch HelmRelease in KSU with inline values
2101 local KUSTOMIZATION_NAME="$5"
2102 local HELMRELEASE_NAME="$6"
2103 local INLINE_VALUES="${7:-""}"
2104 # Secret reference and generation (if required)
2105 local IS_PREEXISTING_SECRET="${8:-"false"}"
2106 local TARGET_NS="$9"
2107 local VALUES_SECRET_NAME="${10}"
2108 local SECRET_KEY="${11:-"values.yaml"}"
2109 local AGE_PUBLIC_KEY="${12}"
2110 ## `SECRET_VALUES` will be obtained from the
2111 ## secret named after the input parameter `reference_secret_for_values`,
2112 ## and from the key named after the input parameter `reference_key_for_values`
2113 local LOCAL_SECRET_VALUES="${13:-"${SECRET_VALUES}"}"
2114 # ConfigMap reference and generation (if required)
2115 local IS_PREEXISTING_CM="${14:-"false"}"
2116 local VALUES_CM_NAME="${15:-""}"
2117 local CM_KEY="${16:-""}"
2118 local CM_VALUES="${17:-""}"
2119 # KSU rendering
2120 local KSU_NAME="${18}"
2121 local PROFILE_NAME="${19}"
2122 local PROFILE_TYPE="${20}"
2123 local PROJECT_NAME="${21:-"osm_admin"}"
2124 ## `FLEET_REPO_DIR` is the result of:
2125 ## "{{inputs.parameters.fleet_mount_path}}/{{inputs.parameters.cloned_fleet_folder_name}}"
2126 local FLEET_REPO_DIR="${22:-"/fleet/fleet-osm/"}"
2127 # local SYNC="${23:-"true"}"
2128
2129
2130 # This function is just an alias of `create_hr_ksu_into_profile`
2131 # forcing synchronization over the KSU folder
2132 create_hr_ksu_into_profile \
2133 "${TEMPLATES_DIR}" \
2134 "${SUBSTITUTE_ENVIRONMENT}" \
2135 "${SUBSTITUTION_FILTER}" \
2136 "${CUSTOM_ENV_VARS}" \
2137 "${KUSTOMIZATION_NAME}" \
2138 "${HELMRELEASE_NAME}" \
2139 "${INLINE_VALUES}" \
2140 "${IS_PREEXISTING_SECRET}" \
2141 "${TARGET_NS}" \
2142 "${VALUES_SECRET_NAME}" \
2143 "${SECRET_KEY}" \
2144 "${AGE_PUBLIC_KEY}" \
2145 "${LOCAL_SECRET_VALUES}" \
2146 "${IS_PREEXISTING_CM}" \
2147 "${VALUES_CM_NAME}" \
2148 "${CM_KEY}" \
2149 "${CM_VALUES}" \
2150 "${KSU_NAME}" \
2151 "${PROFILE_NAME}" \
2152 "${PROFILE_TYPE}" \
2153 "${PROJECT_NAME}" \
2154 "${FLEET_REPO_DIR}" \
2155 "true"
2156}
2157
2158
2159# High-level function to create a "generated" KSU into a profile when:
2160# 1. There is no template (OKA) available.
2161# 2. The SW is based on a Helm Chart that we want to deploy.
2162function create_generated_ksu_from_helm_into_profile() {
2163 # HelmRelease generation
2164 local HELMRELEASE_NAME="$1"
2165 local CHART_NAME="$2"
2166 local CHART_VERSION="$3"
2167 local TARGET_NS="$4"
2168 local CREATE_NS="${5:-"true"}"
2169 # Repo source generation
2170 local IS_PREEXISTING_REPO="${6:-"false"}"
2171 local HELMREPO_NAME="$7"
2172 local HELMREPO_URL="${8:-""}"
2173 local HELMREPO_NS="${9:-"${TARGET_NS}"}"
2174 local HELMREPO_SECRET_REF="${10:-""}"
2175 # HelmRelease inline values (if any)
2176 local INLINE_VALUES="${11:-""}"
2177 # Secret reference and generation (if required)
2178 local IS_PREEXISTING_SECRET="${12:-"false"}"
2179 local VALUES_SECRET_NAME="${13}"
2180 local SECRET_KEY="${14:-"values.yaml"}"
2181 local AGE_PUBLIC_KEY="${15}"
2182 ## `SECRET_VALUES` will be obtained from the
2183 ## secret named after the input parameter `reference_secret_for_values`,
2184 ## and from the key named after the input parameter `reference_key_for_values`
2185 local LOCAL_SECRET_VALUES="${16:-"${SECRET_VALUES}"}"
2186 # ConfigMap reference and generation (if required)
2187 local IS_PREEXISTING_CM="${17:-"false"}"
2188 local VALUES_CM_NAME="${18:-""}"
2189 local CM_KEY="${19:-""}"
2190 local CM_VALUES="${20:-""}"
2191 # KSU rendering
2192 local KSU_NAME="${21}"
2193 local PROFILE_NAME="${22}"
2194 local PROFILE_TYPE="${23}"
2195 local PROJECT_NAME="${24:-"osm_admin"}"
2196 ## `FLEET_REPO_DIR` is the result of:
2197 ## "{{inputs.parameters.fleet_mount_path}}/{{inputs.parameters.cloned_fleet_folder_name}}"
2198 local FLEET_REPO_DIR="${25:-"/fleet/fleet-osm/"}"
2199 # By default, it will not syncronize, so that we can easily accumulate more than
2200 # one Helm chart into the same KSU if desired
2201 local SYNC="${26:-"false"}"
2202
2203 # Decides which steps may be skipped
2204 local NEEDS_NEW_NS=$([[ "${CREATE_NS,,}" == "true" ]]; echo $?)
2205 local NEEDS_NEW_REPO_SOURCE=$([[ "${IS_PREEXISTING_REPO,,}" == "false" ]]; echo $?)
2206 local NEEDS_NEW_SECRET=$([[ ( -n "${VALUES_SECRET_NAME}" ) && ( "${IS_PREEXISTING_SECRET,,}" == "false" ) ]]; echo $?)
2207 local NEEDS_NEW_CM=$([[ ( -n "${VALUES_CM_NAME}" ) && ( "${IS_PREEXISTING_CM,,}" == "false" ) ]]; echo $?)
2208 local ECHO_RESOURCELIST=$([[ "${DEBUG,,}" == "true" ]]; echo $?)
2209
2210 # Determine extra options for HelmRelease creation and define full command
2211 OPTION_CHART_VERSION=""
2212 [[ -n "${CHART_VERSION}" ]] && OPTION_CHART_VERSION='--chart-version=${CHART_VERSION}'
2213 OPTION_INLINE_VALUES=""
2214 [[ -n "${INLINE_VALUES}" ]] && OPTION_INLINE_VALUES='--values=<(
2215 echo "${INLINE_VALUES}"
2216 )'
2217 OPTION_REFERENCE_SECRET=""
2218 [[ -n "${VALUES_SECRET_NAME}" ]] && OPTION_REFERENCE_SECRET='--values-from=Secret/${VALUES_SECRET_NAME}'
2219 OPTION_REFERENCE_CM=""
2220 [[ -n "${VALUES_CM_NAME}" ]] && OPTION_REFERENCE_CM='--values-from=ConfigMap/${VALUES_CM_NAME}'
2221
2222 export HR_COMMAND="\
2223 flux \
2224 -n "${TARGET_NS}" \
2225 create hr "${HELMRELEASE_NAME}" \
2226 --chart="${CHART_NAME}" \
2227 --source=HelmRepository/"${HELMREPO_NAME}.${HELMREPO_NS}" \
2228 "${OPTION_CHART_VERSION}" \
2229 "${OPTION_INLINE_VALUES}" \
2230 "${OPTION_REFERENCE_SECRET}" \
2231 "${OPTION_REFERENCE_CM}" \
2232 --export
2233 "
2234
2235 # Determine extra options for Helm source repo creation and define full command
2236 OPTION_REPO_SECRET=""
2237 [[ -n "${HELMREPO_SECRET_REF}" ]] && OPTION_REPO_SECRET='--secret-ref=${HELMREPO_SECRET_REF}'
2238
2239 export REPO_COMMAND="\
2240 flux \
2241 -n "${HELMREPO_NS}" \
2242 create source helm "${HELMREPO_NAME}" \
2243 --url="${HELMREPO_URL}" \
2244 "${OPTION_REPO_SECRET}" \
2245 --export
2246 "
2247
2248 # Runs workflow
2249 echo "" | \
2250 make_generator \
2251 "helm-release.yaml" \
2252 eval "${HR_COMMAND}" | \
2253 transform_if \
2254 "${NEEDS_NEW_NS}" \
2255 make_generator \
2256 "ns-for-hr.yaml" \
2257 kubectl \
2258 create \
2259 namespace \
2260 "${TARGET_NS}" \
2261 -o=yaml \
2262 --dry-run=client | \
2263 transform_if \
2264 "${NEEDS_NEW_REPO_SOURCE}" \
2265 make_generator \
2266 "helm-repo.yaml" \
2267 eval "${REPO_COMMAND}" | \
2268 transform_if \
2269 "${NEEDS_NEW_SECRET}" \
2270 make_generator \
2271 "hr-values-secret.yaml" \
2272 kubectl_encrypt \
2273 "${AGE_PUBLIC_KEY}" \
2274 create \
2275 secret \
2276 generic \
2277 "${VALUES_SECRET_NAME}" \
2278 --namespace="${TARGET_NS}" \
2279 --from-file="${SECRET_KEY}"=<(echo "${LOCAL_SECRET_VALUES}") \
2280 -o=yaml \
2281 --dry-run=client | \
2282 transform_if \
2283 "${NEEDS_NEW_CM}" \
2284 make_generator \
2285 "hr-values-configmap.yaml" \
2286 kubectl \
2287 create \
2288 configmap \
2289 "${VALUES_CM_NAME}" \
2290 --namespace="${TARGET_NS}" \
2291 --from-file="${SECRET_KEY}"=<(echo "${CM_VALUES}") \
2292 -o=yaml \
2293 --dry-run=client | \
2294 transform_if \
2295 "${ECHO_RESOURCELIST}" \
2296 tee /dev/stderr | \
2297 render_ksu_into_profile \
2298 "${KSU_NAME}" \
2299 "${PROFILE_NAME}" \
2300 "${PROFILE_TYPE}" \
2301 "${PROJECT_NAME}" \
2302 "${FLEET_REPO_DIR}" \
2303 "${SYNC}"
2304}
2305
2306
2307# High-level function to update a "generated" KSU:
2308# 1. There is no template (OKA) available.
2309# 2. The SW is based on a Helm Chart that we want to deploy.
2310# NOTE: It is an alias of `create_generated_ksu_from_helm_into_profile`, setting `sync` to true
2311function update_generated_ksu_from_helm_into_profile() {
2312 # HelmRelease generation
2313 local HELMRELEASE_NAME="$1"
2314 local CHART_NAME="$2"
2315 local CHART_VERSION="$3"
2316 local TARGET_NS="$4"
2317 local CREATE_NS="${5:-"true"}"
2318 # Repo source generation
2319 local IS_PREEXISTING_REPO="${6:-"false"}"
2320 local HELMREPO_NAME="$7"
2321 local HELMREPO_URL="${8:-""}"
2322 local HELMREPO_NS="${9:-"${TARGET_NS}"}"
2323 local HELMREPO_SECRET_REF="${10:-""}"
2324 # HelmRelease inline values (if any)
2325 local INLINE_VALUES="${11:-""}"
2326 # Secret reference and generation (if required)
2327 local IS_PREEXISTING_SECRET="${12:-"false"}"
2328 local VALUES_SECRET_NAME="${13}"
2329 local SECRET_KEY="${14:-"values.yaml"}"
2330 local AGE_PUBLIC_KEY="${15}"
2331 ## `SECRET_VALUES` will be obtained from the
2332 ## secret named after the input parameter `reference_secret_for_values`,
2333 ## and from the key named after the input parameter `reference_key_for_values`
2334 local LOCAL_SECRET_VALUES="${16:-"${SECRET_VALUES}"}"
2335 # ConfigMap reference and generation (if required)
2336 local IS_PREEXISTING_CM="${17:-"false"}"
2337 local VALUES_CM_NAME="${18:-""}"
2338 local CM_KEY="${19:-""}"
2339 local CM_VALUES="${20:-""}"
2340 # KSU rendering
2341 local KSU_NAME="${21}"
2342 local PROFILE_NAME="${22}"
2343 local PROFILE_TYPE="${23}"
2344 local PROJECT_NAME="${24:-"osm_admin"}"
2345 ## `FLEET_REPO_DIR` is the result of:
2346 ## "{{inputs.parameters.fleet_mount_path}}/{{inputs.parameters.cloned_fleet_folder_name}}"
2347 local FLEET_REPO_DIR="${25:-"/fleet/fleet-osm/"}"
2348 # By default, it will not syncronize, so that we can easily accumulate more than
2349 # one Helm chart into the same KSU if desired
2350 # local SYNC="${26:-"false"}"
2351
2352 # Decides which steps may be skipped
2353 local NEEDS_NEW_NS=$([[ "${CREATE_NS,,}" == "true" ]]; echo $?)
2354 local NEEDS_NEW_REPO_SOURCE=$([[ "${IS_PREEXISTING_REPO,,}" == "false" ]]; echo $?)
2355 local NEEDS_NEW_SECRET=$([[ ( -n "${VALUES_SECRET_NAME}" ) && ( "${IS_PREEXISTING_SECRET,,}" == "false" ) ]]; echo $?)
2356 local NEEDS_NEW_CM=$([[ ( -n "${VALUES_CM_NAME}" ) && ( "${IS_PREEXISTING_CM,,}" == "false" ) ]]; echo $?)
2357 local ECHO_RESOURCELIST=$([[ "${DEBUG,,}" == "true" ]]; echo $?)
2358
2359
2360 # This function is just an alias of `create_generated_ksu_from_helm_into_profile`
2361 # forcing synchronization over the KSU folder
2362 create_generated_ksu_from_helm_into_profile \
2363 "${HELMRELEASE_NAME}" \
2364 "${CHART_NAME}" \
2365 "${CHART_VERSION}" \
2366 "${TARGET_NS}" \
2367 "${CREATE_NS}" \
2368 "${IS_PREEXISTING_REPO}" \
2369 "${HELMREPO_NAME}" \
2370 "${HELMREPO_URL}" \
2371 "${HELMREPO_NS}" \
2372 "${HELMREPO_SECRET_REF}" \
2373 "${INLINE_VALUES}" \
2374 "${IS_PREEXISTING_SECRET}" \
2375 "${VALUES_SECRET_NAME}" \
2376 "${SECRET_KEY}" \
2377 "${AGE_PUBLIC_KEY}" \
2378 "${LOCAL_SECRET_VALUES}" \
2379 "${IS_PREEXISTING_CM}" \
2380 "${VALUES_CM_NAME}" \
2381 "${CM_KEY}" \
2382 "${CM_VALUES}" \
2383 "${KSU_NAME}" \
2384 "${PROFILE_NAME}" \
2385 "${PROFILE_TYPE}" \
2386 "${PROJECT_NAME}" \
2387 "${FLEET_REPO_DIR}" \
2388 "true"
2389}
2390
2391
2392# Low-level function to delete a KSU from a profile
2393function delete_ksu_from_profile_path() {
2394 local KSU_NAME="$1"
2395 local TARGET_PROFILE_PATH="$2"
2396 local FLEET_REPO_DIR="${3:-"${FLEET_REPO_DIR}"}"
2397
2398 # Calculate profile folder
2399 TARGET_PROFILE_FOLDER="${FLEET_REPO_DIR}/${TARGET_PROFILE_PATH}"
2400
2401 # Delete the KSU folder
2402 rm -rf "${TARGET_PROFILE_FOLDER}/${KSU_NAME}"
2403}
2404
2405
2406# High-level function to delete a KSU from a profile
2407function delete_ksu_from_profile() {
2408 local KSU_NAME="$1"
2409 local PROFILE_NAME="$2"
2410 local PROFILE_TYPE="$3"
2411 local PROJECT_NAME="${4:-"osm_admin"}"
2412 ## `FLEET_REPO_DIR` is the result of:
2413 ## "{{inputs.parameters.fleet_mount_path}}/{{inputs.parameters.cloned_fleet_folder_name}}"
2414 local FLEET_REPO_DIR="$5"
2415
2416 # Calculate profile folder
2417 local TARGET_PROFILE_PATH=$(
2418 path_to_profile \
2419 "${PROFILE_NAME}" \
2420 "${PROFILE_TYPE}" \
2421 "${PROJECT_NAME}"
2422 )
2423 TARGET_PROFILE_FOLDER="${FLEET_REPO_DIR}/${TARGET_PROFILE_PATH}"
2424
2425 # Delete the KSU folder
2426 rm -rf "${TARGET_PROFILE_FOLDER}/${KSU_NAME}"
2427}
2428
2429
2430# High-level function to clone a KSU from a profile to another
2431function clone_ksu() {
2432 local SOURCE_KSU_NAME="$1"
2433 local SOURCE_PROFILE_NAME="$2"
2434 local SOURCE_PROFILE_TYPE="$3"
2435 local SOURCE_PROJECT_NAME="${4:-"osm_admin"}"
2436 local DESTINATION_KSU_NAME="${5:-"${SOURCE_KSU_NAME}"}"
2437 local DESTINATION_PROFILE_NAME="${6:-"${SOURCE_PROFILE_NAME}"}"
2438 local DESTINATION_PROFILE_TYPE="${7:-"${SOURCE_PROFILE_TYPE}"}"
2439 local DESTINATION_PROJECT_NAME="${8:-"${SOURCE_PROJECT_NAME}"}"
2440 ## `FLEET_REPO_DIR` is the result of:
2441 ## "{{inputs.parameters.fleet_mount_path}}/{{inputs.parameters.cloned_fleet_folder_name}}"
2442 local FLEET_REPO_DIR="$9"
2443
2444
2445 # If source and destination are identical, aborts
2446 if [[
2447 ("${SOURCE_KSU_NAME}" == "${DESTINATION_KSU_NAME}") && \
2448 ("${SOURCE_PROFILE_NAME}" == "${DESTINATION_PROFILE_NAME}") && \
2449 ("${SOURCE_PROFILE_TYPE}" == "${DESTINATION_PROFILE_TYPE}") && \
2450 ("${SOURCE_PROJECT_NAME}" == "${DESTINATION_PROJECT_NAME}") \
2451 ]];
2452 then
2453 return 1
2454 fi
2455
2456 # Calculate profile folders
2457 local SOURCE_PROFILE_PATH=$(
2458 path_to_profile \
2459 "${SOURCE_PROFILE_NAME}" \
2460 "${SOURCE_PROFILE_TYPE}" \
2461 "${SOURCE_PROJECT_NAME}"
2462 )
2463 local SOURCE_PROFILE_FOLDER="${FLEET_REPO_DIR}/${SOURCE_PROFILE_PATH}"
2464 local DESTINATION_PROFILE_PATH=$(
2465 path_to_profile \
2466 "${DESTINATION_PROFILE_NAME}" \
2467 "${DESTINATION_PROFILE_TYPE}" \
2468 "${DESTINATION_PROJECT_NAME}"
2469 )
2470 local DESTINATION_PROFILE_FOLDER="${FLEET_REPO_DIR}/${DESTINATION_PROFILE_PATH}"
2471
2472 # Clone KSU folder
2473 cp -ar \
2474 "${SOURCE_PROFILE_FOLDER}/${SOURCE_KSU_NAME}" \
2475 "${DESTINATION_PROFILE_FOLDER}/${DESTINATION_KSU_NAME}"
2476}
2477
2478
2479# Create a `ProviderConfig` for a CrossPlane provider
2480function create_crossplane_providerconfig() {
2481 local PROVIDERCONFIG_NAME="$1"
2482 # As of today, one among `azure`, `aws` or `gcp`:
2483 local PROVIDER_TYPE="$2"
2484 local CRED_SECRET_NAME="$3"
2485 local CRED_SECRET_KEY="${4:-"creds"}"
2486 local CRED_SECRET_NS="${5:-"crossplane-system"}"
2487 # If empty, it assumes the secret already exists
2488 local CRED_SECRET_CONTENT="${6:-"${CRED_SECRET_CONTENT:-""}"}"
2489 local AGE_PUBLIC_KEY_MGMT="$7"
2490 ## `FLEET_REPO_DIR` is the result of:
2491 ## "{{inputs.parameters.fleet_mount_path}}/{{inputs.parameters.cloned_fleet_folder_name}}"
2492 local FLEET_REPO_DIR="${8:-"${FLEET_REPO_DIR}"}"
2493 ## `SW_CATALOGS_REPO_DIR` is the result of:
2494 ## "{{inputs.parameters.sw_catalogs_mount_path}}/{{inputs.parameters.cloned_sw_catalogs_folder_name}}"
2495 local SW_CATALOGS_REPO_DIR="${9:-"${SW_CATALOGS_REPO_DIR}"}"
2496 # Only when applicable
2497 local TARGET_GCP_PROJECT="${10:-""}"
2498 # Do not touch unless strictly needed
2499 local BASE_TEMPLATES_PATH="${11:-"infra-configs/crossplane/providers"}"
2500 local OSM_PROJECT_NAME="${12:-"osm_admin"}"
2501 local MGMT_CLUSTER_NAME="${13:-"_management"}"
2502
garciadeblas70461c52024-07-03 09:17:56 +02002503 # Is the provider type supported?
2504 local VALID_PROVIDERS=("aws" "azure" "gcp")
2505 PROVIDER_TYPE="${PROVIDER_TYPE,,}"
2506 [[ ! ($(echo ${VALID_PROVIDERS[@]} | grep -w "${PROVIDER_TYPE}")) ]] && return 1
2507
2508 # Determines the source dir for the templates and the target folder in Fleet
2509 local TEMPLATES_DIR="${SW_CATALOGS_REPO_DIR}/${BASE_TEMPLATES_PATH}/${PROVIDER_TYPE}/templates"
2510 local TARGET_FOLDER="${FLEET_REPO_DIR}/${OSM_PROJECT_NAME}/infra-config-profiles/${MGMT_CLUSTER_NAME}/crossplane-providerconfigs/${PROVIDER_TYPE}"
2511
2512 # Determine which optional steps may be needed
2513 local NEEDS_NEW_SECRET=$([[ -n "${CRED_SECRET_CONTENT}" ]]; echo $?)
2514 local NEEDS_PROJECT_NAME=$([[ "${PROVIDER_TYPE}" == "gcp" ]]; echo $?)
2515
2516 # Renders the `ProviderConfig` manifest and the encrypted secret (if applicable)
2517 echo "" | \
2518 folder2list_generator \
2519 "${TEMPLATES_DIR}" | \
2520 patch_replace \
2521 ".metadata.name" \
2522 "${PROVIDERCONFIG_NAME}" \
2523 "| select(.kind == \"ProviderConfig\")" | \
2524 patch_replace \
2525 ".spec.credentials.secretRef.name" \
2526 "${CRED_SECRET_NAME}" \
2527 "| select(.kind == \"ProviderConfig\")" | \
2528 patch_replace \
2529 ".spec.credentials.secretRef.key" \
2530 "${CRED_SECRET_KEY}" \
2531 "| select(.kind == \"ProviderConfig\")" | \
2532 patch_replace \
2533 ".spec.credentials.secretRef.namespace" \
2534 "${CRED_SECRET_NS}" \
2535 "| select(.kind == \"ProviderConfig\")" | \
2536 transform_if \
2537 "${NEEDS_PROJECT_NAME}" \
2538 patch_replace \
2539 ".spec.projectID" \
2540 "${TARGET_GCP_PROJECT}" \
2541 "| select(.kind == \"ProviderConfig\")" | \
2542 transform_if \
2543 "${NEEDS_NEW_SECRET}" \
2544 make_generator \
2545 "credentials-secret.yaml" \
2546 kubectl_encrypt \
2547 "${AGE_PUBLIC_KEY_MGMT}" \
2548 create \
2549 secret \
2550 generic \
2551 "${CRED_SECRET_NAME}" \
2552 --namespace="${CRED_SECRET_NS}" \
2553 --from-file="${CRED_SECRET_KEY}"=<(echo "${CRED_SECRET_CONTENT}") \
2554 -o=yaml \
2555 --dry-run=client | \
2556 prepend_folder_path \
2557 "${PROVIDERCONFIG_NAME}/" | \
2558 list2folder_cp_over \
2559 "${TARGET_FOLDER}"
2560}
2561
2562
2563# Delete a `ProviderConfig` for a CrossPlane provider
2564function delete_crossplane_providerconfig() {
2565 local PROVIDERCONFIG_NAME="$1"
2566 # As of today, one among `azure`, `aws` or `gcp`:
2567 local PROVIDER_TYPE="$2"
2568 ## `FLEET_REPO_DIR` is the result of:
2569 ## "{{inputs.parameters.fleet_mount_path}}/{{inputs.parameters.cloned_fleet_folder_name}}"
2570 local FLEET_REPO_DIR="${3:-"${FLEET_REPO_DIR}"}"
2571 # Do not touch unless strictly needed
2572 local OSM_PROJECT_NAME="${4:-"osm_admin"}"
2573 local MGMT_CLUSTER_NAME="${5:-"_management"}"
2574
garciadeblas70461c52024-07-03 09:17:56 +02002575 # Is the provider type supported?
2576 local VALID_PROVIDERS=("aws" "azure" "gcp")
2577 PROVIDER_TYPE="${PROVIDER_TYPE,,}"
2578 [[ ! ($(echo ${VALID_PROVIDERS[@]} | grep -w "${PROVIDER_TYPE}")) ]] && return 1
2579
2580 # Determines the target folder in Fleet
2581 local PROVIDERCONFIG_FOLDER="${FLEET_REPO_DIR}/${OSM_PROJECT_NAME}/infra-config-profiles/${MGMT_CLUSTER_NAME}/crossplane-providerconfigs/${PROVIDER_TYPE}/${PROVIDERCONFIG_NAME}"
2582
2583 # Delete the folder
2584 rm -rf "${PROVIDERCONFIG_FOLDER}"
2585}
2586
2587
2588# Update a `ProviderConfig` for a CrossPlane provider
2589function update_crossplane_providerconfig() {
2590 local PROVIDERCONFIG_NAME="$1"
2591 # As of today, one among `azure`, `aws` or `gcp`:
2592 local PROVIDER_TYPE="$2"
2593 local CRED_SECRET_NAME="$3"
2594 local CRED_SECRET_KEY="${4:-"creds"}"
2595 local CRED_SECRET_NS="${5:-"crossplane-system"}"
2596 # If empty, it assumes the secret already exists
2597 local CRED_SECRET_CONTENT="${6:-"${CRED_SECRET_CONTENT:-""}"}"
2598 local AGE_PUBLIC_KEY_MGMT="$7"
2599 ## `FLEET_REPO_DIR` is the result of:
2600 ## "{{inputs.parameters.fleet_mount_path}}/{{inputs.parameters.cloned_fleet_folder_name}}"
2601 local FLEET_REPO_DIR="${8:-"${FLEET_REPO_DIR}"}"
2602 ## `SW_CATALOGS_REPO_DIR` is the result of:
2603 ## "{{inputs.parameters.sw_catalogs_mount_path}}/{{inputs.parameters.cloned_sw_catalogs_folder_name}}"
2604 local SW_CATALOGS_REPO_DIR="${9:-"${SW_CATALOGS_REPO_DIR}"}"
2605 # Only when applicable
2606 local TARGET_GCP_PROJECT="${10:-""}"
2607 # Do not touch unless strictly needed
2608 local BASE_TEMPLATES_PATH="${11:-"infra-configs/crossplane/providers"}"
2609 local OSM_PROJECT_NAME="${12:-"osm_admin"}"
2610 local MGMT_CLUSTER_NAME="${13:-"_management"}"
2611
garciadeblas70461c52024-07-03 09:17:56 +02002612 # Is the provider type supported?
2613 local VALID_PROVIDERS=("aws" "azure" "gcp")
2614 PROVIDER_TYPE="${PROVIDER_TYPE,,}"
2615 [[ ! ($(echo ${VALID_PROVIDERS[@]} | grep -w "${PROVIDER_TYPE}")) ]] && return 1
2616
2617 # First, delete; then, re-create
2618 delete_crossplane_providerconfig \
2619 "${PROVIDERCONFIG_NAME}" \
2620 "${PROVIDER_TYPE}" \
2621 "${FLEET_REPO_DIR}" \
2622 "${OSM_PROJECT_NAME}" \
2623 "${MGMT_CLUSTER_NAME}"
2624
2625 create_crossplane_providerconfig \
2626 "${PROVIDERCONFIG_NAME}" \
2627 "${PROVIDER_TYPE}" \
2628 "${CRED_SECRET_NAME}" \
2629 "${CRED_SECRET_KEY}" \
2630 "${CRED_SECRET_NS}" \
2631 "${CRED_SECRET_CONTENT}" \
2632 "${AGE_PUBLIC_KEY_MGMT}" \
2633 "${FLEET_REPO_DIR}" \
2634 "${SW_CATALOGS_REPO_DIR}" \
2635 "${TARGET_GCP_PROJECT}" \
2636 "${BASE_TEMPLATES_PATH}" \
2637 "${OSM_PROJECT_NAME}" \
2638 "${MGMT_CLUSTER_NAME}"
2639}
2640
2641
garciadeblas8a28f6d2025-06-11 11:11:56 +02002642# Create a CloudConfig for CAPI provider
2643function create_capi_openstack_cloudconf() {
2644 local OPENSTACK_CLOUD_NAME="${1}"
2645 local PUBLIC_KEY="${2:-"${PUBLIC_KEY_MGMT}"}"
2646 local CONFIG_DIR="${3:-"${MGMT_ADDON_CONFIG_DIR}"}"
2647
2648 local NAMESPACE="managed-resources"
2649
2650 local CLOUDS_YAML="${OPENSTACK_CLOUDS_YAML}"
2651 local CACERT="${OPENSTACK_CACERT}"
2652
2653 local CLOUD_CREDENTIALS_SECRET_NAME="${OPENSTACK_CLOUD_NAME}-capo-config"
2654 local CLOUD_CREDENTIALS_CLOUDS_KEY="clouds.yaml"
2655 local CLOUD_CREDENTIALS_CACERT_KEY="cacert"
2656 local CLOUD_CREDENTIALS_FILENAME="credentials-secret.yaml"
garciadeblas94e638f2025-08-06 17:53:22 +02002657
garciadeblas8a28f6d2025-06-11 11:11:56 +02002658 local CLOUD_CREDENTIALS_TOML_SECRET_NAME="${OPENSTACK_CLOUD_NAME}-capo-config-toml"
2659 local CLOUD_CREDENTIALS_TOML_FILENAME="credentials-toml-secret.yaml"
2660
2661 local TARGET_FOLDER="${CONFIG_DIR}/capi-providerconfigs/capo/${OPENSTACK_CLOUD_NAME}-config"
2662 mkdir -p "${TARGET_FOLDER}"
2663
2664 echo "" | \
2665 make_generator \
2666 "${CLOUD_CREDENTIALS_FILENAME}" \
2667 kubectl_encrypt \
2668 "${PUBLIC_KEY}" \
2669 create \
2670 secret \
2671 generic \
2672 "${CLOUD_CREDENTIALS_SECRET_NAME}" \
2673 --namespace="${NAMESPACE}" \
2674 --from-file="${CLOUD_CREDENTIALS_CLOUDS_KEY}"=<(echo "${CLOUDS_YAML}") \
2675 --from-file="${CLOUD_CREDENTIALS_CACERT_KEY}"=<(echo "${CACERT}") \
2676 -o=yaml \
2677 --dry-run=client | \
2678 make_generator \
2679 "${CLOUD_CREDENTIALS_TOML_FILENAME}" \
2680 kubectl_encrypt \
2681 "${PUBLIC_KEY}" \
2682 create \
2683 secret \
2684 generic \
2685 "${CLOUD_CREDENTIALS_TOML_SECRET_NAME}" \
2686 --namespace="${NAMESPACE}" \
2687 --from-file="os_auth_url"=<(echo "${OS_AUTH_URL}") \
2688 --from-file="os_region_name"=<(echo "${OS_REGION_NAME}") \
2689 --from-file="os_username"=<(echo "${OS_USERNAME}") \
2690 --from-file="os_password"=<(echo "${OS_PASSWORD}") \
2691 --from-file="os_project_id"=<(echo "${OS_PROJECT_ID}") \
2692 --from-file="os_project_domain_id"=<(echo "${OS_PROJECT_DOMAIN_ID}") \
2693 -o=yaml \
2694 --dry-run=client | \
2695 list2folder_cp_over \
2696 "${TARGET_FOLDER}"
2697}
2698
2699# Update a CloudConfig for CAPI provider
2700function update_capi_openstack_cloudconf() {
2701 local CLOUD_CONFIG_NAME="${1}"
2702 local PUBLIC_KEY="${2:-"${PUBLIC_KEY_MGMT}"}"
2703 local CONFIG_DIR="${3:-"${MGMT_ADDON_CONFIG_DIR}"}"
2704
2705 delete_capi_openstack_cloudconf \
2706 "${CLOUD_CONFIG_NAME}" \
2707 "${CONFIG_DIR}"
garciadeblas94e638f2025-08-06 17:53:22 +02002708
garciadeblas8a28f6d2025-06-11 11:11:56 +02002709 create_capi_openstack_cloudconf \
2710 "${CLOUD_CONFIG_NAME}" \
2711 "${PUBLIC_KEY}" \
2712 "${CONFIG_DIR}"
2713}
2714
2715
2716# Delete a CloudConfig for CAPI provider
2717function delete_capi_openstack_cloudconf() {
2718 local OPENSTACK_CLOUD_NAME="$1"
2719 local CONFIG_DIR="${2:-"${MGMT_ADDON_CONFIG_DIR}"}"
2720
2721 local TARGET_FOLDER="${CONFIG_DIR}/capi-providerconfigs/capo/${OPENSTACK_CLOUD_NAME}-config"
garciadeblas94e638f2025-08-06 17:53:22 +02002722
garciadeblas8a28f6d2025-06-11 11:11:56 +02002723 # Delete the encrypted secrets files.
2724 rm -rf "${TARGET_FOLDER}"
2725}
2726
garciadeblas94e638f2025-08-06 17:53:22 +02002727
garciadeblas70461c52024-07-03 09:17:56 +02002728# Helper function to return the relative path of a location in SW Catalogs for an OKA
2729function path_to_catalog() {
2730 local OKA_TYPE="$1"
2731 local PROJECT_NAME="${2:-"osm_admin"}"
2732
2733 # Corrects `osm_admin` project, since it uses the root folder
2734 PROJECT_NAME="${PROJECT_NAME}"
2735 [[ "${PROJECT_NAME}" == "osm_admin" ]] && PROJECT_NAME="."
2736
2737 # Echoes the relate path from the SW-Catalogs root
2738 case "${OKA_TYPE,,}" in
2739
2740 "controller" | "infra-controller" | "infra-controllers" | "infra_controller" | "infra_controllers")
2741 echo -n "${PROJECT_NAME}/infra-controllers"
2742 return 0
2743 ;;
2744
2745 "config" | "infra-config" | "infra-configs" | "infra_config" | "infra_configs")
2746 echo -n "${PROJECT_NAME}/infra-configs"
2747 return 0
2748 ;;
2749
2750 "managed" | "resources" | "managed-resources" | "managed_resources" | "cloud-resources" | "cloud_resources")
2751 echo -n "${PROJECT_NAME}/cloud-resources"
2752 return 0
2753 ;;
2754
2755 "app" |"apps" | "applications" | "cnf" | "cnfs" | "nf" | "nfs")
2756 echo -n "${PROJECT_NAME}/apps"
2757 return 0
2758 ;;
2759
2760 *)
2761 echo -n "------------ ERROR ------------"
2762 return 1
2763 ;;
2764 esac
2765}
2766
2767
2768# Create OKA of a specific kind
2769function create_oka() {
2770 local OKA_NAME="$1"
2771 local OKA_TYPE="$2"
2772 local PROJECT_NAME="${3:-"."}"
2773 ## `SW_CATALOGS_REPO_DIR` is the result of:
2774 ## "{{inputs.parameters.sw_catalogs_mount_path}}/{{inputs.parameters.cloned_sw_catalogs_folder_name}}"
2775 local SW_CATALOGS_REPO_DIR="$4"
2776 local OKA_LOCATION="${5:-"."}"
2777 local TARBALL_FILE="${6:-"true"}"
2778
2779
2780 # Finds the corresponding catalog path from the SW-Catalogs root
2781 # and create the destination
2782 local CATALOG_PATH=$(\
2783 path_to_catalog \
2784 "${OKA_TYPE}" \
2785 "${PROJECT_NAME}"
2786 )
2787 local DESTINATION="${SW_CATALOGS_REPO_DIR}/${CATALOG_PATH}/${OKA_NAME}"
2788 mkdir -p "${DESTINATION}"
2789
2790 # When the OKA comes as a `tar.gz`
2791 if [[ "${TARBALL_FILE,,}" == "true" ]];
2792 then
2793 tar xvfz "${OKA_LOCATION}/${OKA_NAME}.tar.gz" -C "${DESTINATION}"
2794 else
2795 # Otherwise it must be a folder structure
2796 cp -var "${OKA_LOCATION}/${OKA_NAME}/*" "${DESTINATION}/"
2797 fi
2798}
2799
2800
2801# Delete OKA of a specific kind
2802function delete_oka() {
2803 local OKA_NAME="$1"
2804 local OKA_TYPE="$2"
2805 local PROJECT_NAME="${3:-"."}"
2806 ## `SW_CATALOGS_REPO_DIR` is the result of:
2807 ## "{{inputs.parameters.sw_catalogs_mount_path}}/{{inputs.parameters.cloned_sw_catalogs_folder_name}}"
2808 local SW_CATALOGS_REPO_DIR="$4"
2809
2810
2811 # Finds the corresponding catalog path from the SW-Catalogs root
2812 # and determine the destination
2813 local CATALOG_PATH=$(\
2814 path_to_catalog \
2815 "${OKA_TYPE}" \
2816 "${PROJECT_NAME}"
2817 )
2818 local DESTINATION="${SW_CATALOGS_REPO_DIR}/${CATALOG_PATH}/${OKA_NAME}"
2819
2820 # Remove the folder
2821 rm -rf "${DESTINATION}"
2822}
2823
2824
2825# Update OKA of a specific kind
2826function update_oka() {
2827 local OKA_NAME="$1"
2828 local OKA_TYPE="$2"
2829 local PROJECT_NAME="${3:-"."}"
2830 ## `SW_CATALOGS_REPO_DIR` is the result of:
2831 ## "{{inputs.parameters.sw_catalogs_mount_path}}/{{inputs.parameters.cloned_sw_catalogs_folder_name}}"
2832 local SW_CATALOGS_REPO_DIR="$4"
2833 local OKA_LOCATION="${5:-"."}"
2834 local TARBALL_FILE="${6:-"true"}"
2835
2836
2837 # Finds the corresponding catalog path from the SW-Catalogs root
2838 # and determine the destination
2839 local CATALOG_PATH=$(\
2840 path_to_catalog \
2841 "${OKA_TYPE}" \
2842 "${PROJECT_NAME}"
2843 )
2844 local DESTINATION="${SW_CATALOGS_REPO_DIR}/${CATALOG_PATH}/${OKA_NAME}"
2845
2846 # Remove and re-create
2847 rm -rf "${DESTINATION}"
2848 create_oka \
2849 "${OKA_NAME}" \
2850 "${OKA_TYPE}" \
2851 "${PROJECT_NAME}" \
2852 "${SW_CATALOGS_REPO_DIR}" \
2853 "${OKA_LOCATION}" \
2854 "${TARBALL_FILE}"
2855}