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