From 029b28a8c434bfc3a1bdcfb15960cd52c6608dbd Mon Sep 17 00:00:00 2001 From: marcstef Date: Tue, 4 Nov 2025 23:58:02 +0000 Subject: [PATCH 1/4] development branch for OrdsSrvs Controller new features Native Kubernetes Secrets - encPrivKey attribute is now optional Secret Env Injection - secret values are not shown in describe pod output TNSAdminSecret - documentation page poolWalletSecret - new attribute for external Oracle Wallet --- Dockerfile | 5 +- README.md | 23 + apis/database/v4/ordssrvs_types.go | 30 +- controllers/database/ordssrvs_controller.go | 378 ++++----- controllers/database/ordssrvs_ordsconfig.go | 245 +++--- docs/ordsservices/README.md | 5 + docs/ordsservices/examples/resources.md | 44 ++ ordssrvs/RSADecryptOAEP.java | 66 ++ ordssrvs/ords_init.sh | 822 ++++++++++++++++++++ ordssrvs/ords_start.sh | 26 + 10 files changed, 1324 insertions(+), 320 deletions(-) create mode 100644 docs/ordsservices/examples/resources.md create mode 100644 ordssrvs/RSADecryptOAEP.java create mode 100644 ordssrvs/ords_init.sh create mode 100644 ordssrvs/ords_start.sh diff --git a/Dockerfile b/Dockerfile index 299e0e6f..ef2bd9c5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -57,8 +57,9 @@ ENV COMMIT_SHA=${CI_COMMIT_SHA} \ COMMIT_BRANCH=${CI_COMMIT_BRANCH} WORKDIR / COPY --from=builder /workspace/manager . -COPY ords/ords_init.sh . -COPY ords/ords_start.sh . +COPY ordssrvs/ords_init.sh /ordssrvs/ +COPY ordssrvs/ords_start.sh /ordssrvs/ +COPY ordssrvs/RSADecryptOAEP.java /ordssrvs/ COPY LICENSE.txt /licenses/ COPY THIRD_PARTY_LICENSES_DOCKER.txt /licenses/ COPY THIRD_PARTY_LICENSES.txt /licenses/ diff --git a/README.md b/README.md index da6a179e..0de4b13f 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,26 @@ +># ⚠️ **OrdsSrvs Controller Development Branch** ⚠️ +> +>This is a development branch for **OrdsSrvs Controller new features**. It is not the main/stable branch and should not be used for production. +> +>**CHANGELOG** +>* **Standard Kubernetes Secret** +>**encPrivKey** attribute is now optional; passwords can be stored in native K8s Secrets without additional encryption. +>* **Secret Env Injection** +>Passwords are injected via Kubernetes Secrets into the init container. Secret values are not shown in kubectl describe pod output; only references (e.g., Secret names/keys) may appear. +>* **TNSAdminSecret** +>adding a dedicated documentation page. see [resources](./docs/ordsservices/examples/resources.md") +>* **poolWalletSecret** +>New attribute poolWalletSecret lets you reference an external Oracle Wallet that contains the database credentials, avoiding storage of the password in a Kubernetes Secret. +see [resources](./docs/ordsservices/examples/resources.md") +> +>**HOWTO** +>To build your own Oracle Operator image use this: +>```bash +>make generate manifests install operator-yaml image-build IMG=\ +>``` + + + # Oracle Database Operator for Kubernetes ## Make Oracle Database Kubernetes Native diff --git a/apis/database/v4/ordssrvs_types.go b/apis/database/v4/ordssrvs_types.go index d7e5156a..61ffb210 100644 --- a/apis/database/v4/ordssrvs_types.go +++ b/apis/database/v4/ordssrvs_types.go @@ -77,6 +77,10 @@ type OrdsSrvsSpec struct { } type GlobalSettings struct { + + // Specifies whether the Instance API is enabled. + InstanceAPIEnabled *bool `json:"instance.api.enabled,omitempty"` + // Specifies the setting to enable or disable metadata caching. CacheMetadataEnabled *bool `json:"cache.metadata.enabled,omitempty"` @@ -350,13 +354,9 @@ type PoolSettings struct { //+kubebuilder:default:="ORDS_PUBLIC_USER" DBUsername string `json:"db.username,omitempty"` - // Specifies the password of the specified database user. - // Replaced by: DBSecret PasswordSecret `json:"dbSecret"` - // DBPassword struct{} `json:"dbPassword,omitempty"` - - // Specifies the Secret with the dbUsername and dbPassword values + // Specifies the Secret with the db password // for the connection. - DBSecret PasswordSecret `json:"db.secret"` + DBSecret PasswordSecret `json:"db.secret,omitempty"` // Specifies the username for the database account that ORDS uses for administration operations in the database. DBAdminUser string `json:"db.adminUser,omitempty"` @@ -590,10 +590,14 @@ type PoolSettings struct { // DBTnsDirectory string `json:"db.tnsDirectory,omitempty"` */ - // Specifies the Secret containing the TNS_ADMIN directory + // Specifies the Secret containing the TNS_ADMIN directory, expected file tnanames.ora // Replaces: db.tnsDirectory TNSAdminSecret *TNSAdminSecret `json:"tnsAdminSecret,omitempty"` + // Specifies the Secret containing the pool wallet directory, expected file cwallet.sso + // Replaces: db.tnsDirectory + PoolWalletSecret *PoolWalletSecret `json:"poolWalletSecret,omitempty"` + /************************************************* * Disabled /************************************************* @@ -643,13 +647,19 @@ type CertificateSecret struct { CertificateKey string `json:"key"` } -// Defines the secret containing Certificates +// Defines a secret containing tns admin folder (network/admin), e.g. tnsnames.ora type TNSAdminSecret struct { - // Specifies the name of the TNS_ADMIN Secret + // Specifies the name of the Secret SecretName string `json:"secretName"` } -// Defines the secret containing Certificates +// Defines a secret containing pool wallet, Oracle Wallet with credentials, cwallet.sso +type PoolWalletSecret struct { + // Specifies the name of the Secret + SecretName string `json:"secretName"` +} + +// Defines the secret containing wallet.zip type DBWalletSecret struct { // Specifies the name of the Database Wallet Secret SecretName string `json:"secretName"` diff --git a/controllers/database/ordssrvs_controller.go b/controllers/database/ordssrvs_controller.go index db831805..a1e3646f 100644 --- a/controllers/database/ordssrvs_controller.go +++ b/controllers/database/ordssrvs_controller.go @@ -40,14 +40,9 @@ package controllers import ( "context" - "crypto/rand" - "crypto/rsa" "crypto/sha256" - "crypto/x509" - "encoding/base64" "encoding/hex" "encoding/json" - "encoding/pem" "errors" "fmt" "reflect" @@ -86,8 +81,6 @@ const ( targetHTTPPortName = "pod-http-port" targetHTTPSPortName = "pod-https-port" targetMongoPortName = "pod-mongo-port" - globalConfigMapName = "settings-global" - poolConfigPreName = "settings-" // Append PoolName controllerLabelKey = "oracle.com/ords-operator-filter" controllerLabelVal = "oracle-database-operator" specHashLabel = "oracle.com/ords-operator-spec-hash" @@ -105,10 +98,11 @@ const ( ) // Definitions used in the controller -var ordsInitScript string = "" -var ordsStartScript string = "" -var ordsGlobalConfig string = "" +var ordssrvsScriptsConfigMapName = "" +var ordssrvsGlobalSettingsConfigMapName = "" +var RSADecryptOAEPScript string = "" var APEXInstallationExternal string = "false" +var passwordEncryption = true // Trigger a restart of Pods on Config Changes var RestartPods bool = false @@ -155,7 +149,7 @@ func (r *OrdsSrvsReconciler) SetupWithManager(mgr ctrl.Manager) error { } func (r *OrdsSrvsReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - logger := log.FromContext(ctx) + logger := log.FromContext(ctx).WithName("Reconcile") ordssrvs := &dbapi.OrdsSrvs{} // Check if resource exists or was deleted @@ -168,9 +162,11 @@ func (r *OrdsSrvsReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c return ctrl.Result{Requeue: true, RequeueAfter: time.Minute}, err } - ordsInitScript = ordssrvs.Name + "-init-script" - ordsStartScript = ordssrvs.Name + "-start-script" - ordsGlobalConfig = ordssrvs.Name + "-" + globalConfigMapName + // empty encryption key + if ordssrvs.Spec.EncPrivKey == (dbapi.PasswordSecret{}) { + passwordEncryption = false + logger.Info("Encryption key not set (EncPrivKey)") + } // APEXInstallationExternal // true: persistence volume @@ -188,15 +184,12 @@ func (r *OrdsSrvsReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c } } - // ConfigMap - Init Script - if err := r.ConfigMapReconcile(ctx, ordssrvs, ordsInitScript, 0); err != nil { - logger.Error(err, "Error in ConfigMapReconcile (init-script)") - return ctrl.Result{}, err - } + ordssrvsScriptsConfigMapName = ordssrvs.Name + "-scripts-config-map" + ordssrvsGlobalSettingsConfigMapName = ordssrvs.Name + "-global-settings-config-map" - // ConfigMap - Start Script - if err := r.ConfigMapReconcile(ctx, ordssrvs, ordsStartScript, 0); err != nil { - logger.Error(err, "Error in ConfigMapReconcile (start-script)") + // ConfigMap - Scripts + if err := r.ConfigMapReconcile(ctx, ordssrvs, ordssrvsScriptsConfigMapName, 0); err != nil { + logger.Error(err, "Error in ConfigMapReconcile (init-script)") return ctrl.Result{}, err } @@ -211,7 +204,7 @@ func (r *OrdsSrvsReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c } // ConfigMap - Global Settings - if err := r.ConfigMapReconcile(ctx, ordssrvs, ordsGlobalConfig, 0); err != nil { + if err := r.ConfigMapReconcile(ctx, ordssrvs, ordssrvsGlobalSettingsConfigMapName, 0); err != nil { logger.Error(err, "Error in ConfigMapReconcile (Global)") return ctrl.Result{}, err } @@ -220,7 +213,7 @@ func (r *OrdsSrvsReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c definedPools := make(map[string]bool) for i := 0; i < len(ordssrvs.Spec.PoolSettings); i++ { poolName := strings.ToLower(ordssrvs.Spec.PoolSettings[i].PoolName) - poolConfigMapName := ordssrvs.Name + "-" + poolConfigPreName + poolName + poolConfigMapName := "config-database-pool-" + poolName if definedPools[poolConfigMapName] { return ctrl.Result{}, errors.New("poolName: " + poolName + " is not unique") } @@ -707,7 +700,7 @@ func (r *OrdsSrvsReconciler) podTemplateSpecDefine(ords *dbapi.OrdsSrvs, ctx con Name: ords.Name + "-init", ImagePullPolicy: corev1.PullIfNotPresent, SecurityContext: securityContextDefine(), - Command: []string{"/bin/bash", "-c", ordsSABase + "/init/ords_init.sh"}, + Command: []string{"/bin/bash", "-c", ordsSABase + "/scripts/ords_init.sh"}, Env: r.envDefine(ords, true, ctx), VolumeMounts: specVolumeMounts, }}, @@ -717,9 +710,10 @@ func (r *OrdsSrvsReconciler) podTemplateSpecDefine(ords *dbapi.OrdsSrvs, ctx con ImagePullPolicy: corev1.PullIfNotPresent, SecurityContext: securityContextDefine(), Ports: envPorts, - Command: []string{"/bin/bash", "-c", ordsSABase + "/start/ords_start.sh"}, - Env: r.envDefine(ords, false, ctx), - VolumeMounts: specVolumeMounts, + Command: []string{"/bin/bash", "-c", ordsSABase + "/scripts/ords_start.sh"}, + // DEBUG mode, change to false + Env: r.envDefine(ords, true, ctx), + VolumeMounts: specVolumeMounts, }}, ServiceAccountName: ords.Spec.ServiceAccountName, }, @@ -729,26 +723,32 @@ func (r *OrdsSrvsReconciler) podTemplateSpecDefine(ords *dbapi.OrdsSrvs, ctx con } // Volumes -func VolumesDefine(ctx context.Context, ords *dbapi.OrdsSrvs) ([]corev1.Volume, []corev1.VolumeMount) { +func VolumesDefine(ctx context.Context, ordssrvs *dbapi.OrdsSrvs) ([]corev1.Volume, []corev1.VolumeMount) { + + logger := log.FromContext(ctx).WithName("VolumesDefine") + // Initialize the slice to hold specifications var volumes []corev1.Volume var volumeMounts []corev1.VolumeMount - // SecretHelper - secretHelperVolume := volumeBuild(ordsInitScript, "ConfigMap", 0770) - secretHelperVolumeMount := volumeMountBuild(ordsInitScript, ordsSABase+"/init", true) - volumes = append(volumes, secretHelperVolume) - volumeMounts = append(volumeMounts, secretHelperVolumeMount) + // scripts + scriptsVolume := volumeBuild(ordssrvsScriptsConfigMapName, ordssrvsScriptsConfigMapName, "ConfigMap", 0770) + scriptsVolumeMount := volumeMountBuild(ordssrvsScriptsConfigMapName, ordsSABase+"/scripts", true) + volumes = append(volumes, scriptsVolume) + volumeMounts = append(volumeMounts, scriptsVolumeMount) - // start-script - startScriptVolume := volumeBuild(ordsStartScript, "ConfigMap", 0770) - startScriptVolumeMount := volumeMountBuild(ordsStartScript, ordsSABase+"/start", true) - volumes = append(volumes, startScriptVolume) - volumeMounts = append(volumeMounts, startScriptVolumeMount) + if passwordEncryption { + secretName := ordssrvs.Spec.EncPrivKey.SecretName + encryptionKeyVolume := volumeBuild(secretName, secretName, "Secret") + encryptionKeyVolumeMount := volumeMountBuild(secretName, "/opt/oracle/sa/encryptionPrivateKey/", true) + + volumes = append(volumes, encryptionKeyVolume) + volumeMounts = append(volumeMounts, encryptionKeyVolumeMount) + } if APEXInstallationExternal == "true" { // volume for APEX installation, same optional folder as for ORDS image - apexInstallationVolume := APEXInstallationVolumeDefine(ctx, ords) + apexInstallationVolume := APEXInstallationVolumeDefine(ctx, ordssrvs) apexInstallationReadOnly := false apexInstallationVolumeMount := volumeMountBuild(APEXInstallationPV, APEXInstallationMount, apexInstallationReadOnly) volumes = append(volumes, apexInstallationVolume) @@ -756,72 +756,98 @@ func VolumesDefine(ctx context.Context, ords *dbapi.OrdsSrvs) ([]corev1.Volume, } // Build volume specifications for globalSettings - standaloneVolume := volumeBuild("standalone", "EmptyDir") + standaloneVolume := volumeBuild("standalone", "", "EmptyDir") standaloneVolumeMount := volumeMountBuild("standalone", ordsSABase+"/config/global/standalone/", false) - globalWalletVolume := volumeBuild("sa-wallet-global", "EmptyDir") + credentialsVolume := volumeBuild("credentials", "", "EmptyDir") + credentialsVolumeMount := volumeMountBuild("credentials", ordsSABase+"/config/global/credentials/", false) + + globalWalletVolume := volumeBuild("sa-wallet-global", "", "EmptyDir") globalWalletVolumeMount := volumeMountBuild("sa-wallet-global", ordsSABase+"/config/global/wallet/", false) - globalLogVolume := volumeBuild("sa-log-global", "EmptyDir") + globalLogVolume := volumeBuild("sa-log-global", "", "EmptyDir") globalLogVolumeMount := volumeMountBuild("sa-log-global", ordsSABase+"/log/global/", false) - globalConfigVolume := volumeBuild(ordsGlobalConfig, "ConfigMap") - globalConfigVolumeMount := volumeMountBuild(ordsGlobalConfig, ordsSABase+"/config/global/", true) + globalConfigVolume := volumeBuild(ordssrvsGlobalSettingsConfigMapName, ordssrvsGlobalSettingsConfigMapName, "ConfigMap") + globalConfigVolumeMount := volumeMountBuild(ordssrvsGlobalSettingsConfigMapName, ordsSABase+"/config/global/", true) - globalDocRootVolume := volumeBuild("sa-doc-root", "EmptyDir") + globalDocRootVolume := volumeBuild("sa-doc-root", "", "EmptyDir") globalDocRootVolumeMount := volumeMountBuild("sa-doc-root", ordsSABase+"/config/global/doc_root/", false) - volumes = append(volumes, standaloneVolume, globalWalletVolume, globalLogVolume, globalConfigVolume, globalDocRootVolume) - volumeMounts = append(volumeMounts, standaloneVolumeMount, globalWalletVolumeMount, globalLogVolumeMount, globalConfigVolumeMount, globalDocRootVolumeMount) + volumes = append(volumes, standaloneVolume, globalWalletVolume, globalLogVolume, globalConfigVolume, globalDocRootVolume, credentialsVolume) + volumeMounts = append(volumeMounts, standaloneVolumeMount, globalWalletVolumeMount, globalLogVolumeMount, globalConfigVolumeMount, globalDocRootVolumeMount, credentialsVolumeMount) - if ords.Spec.GlobalSettings.CertSecret != nil { - globalCertVolume := volumeBuild(ords.Spec.GlobalSettings.CertSecret.SecretName, "Secret") - globalCertVolumeMount := volumeMountBuild(ords.Spec.GlobalSettings.CertSecret.SecretName, ordsSABase+"/config/certficate/", true) + if ordssrvs.Spec.GlobalSettings.CertSecret != nil { + secretName := ordssrvs.Spec.GlobalSettings.CertSecret.SecretName + globalCertVolume := volumeBuild(secretName, secretName, "Secret") + globalCertVolumeMount := volumeMountBuild(ordssrvs.Spec.GlobalSettings.CertSecret.SecretName, ordsSABase+"/config/certficate/", true) volumes = append(volumes, globalCertVolume) volumeMounts = append(volumeMounts, globalCertVolumeMount) } // Build volume specifications for each pool in poolSettings - definedWalletSecret := make(map[string]bool) - definedTNSSecret := make(map[string]bool) - for i := 0; i < len(ords.Spec.PoolSettings); i++ { - poolName := strings.ToLower(ords.Spec.PoolSettings[i].PoolName) + definedVolumes := make(map[string]bool) - poolWalletName := "sa-wallet-" + poolName - poolWalletVolume := volumeBuild(poolWalletName, "EmptyDir") - poolWalletVolumeMount := volumeMountBuild(poolWalletName, ordsSABase+"/config/databases/"+poolName+"/wallet/", false) + for i := 0; i < len(ordssrvs.Spec.PoolSettings); i++ { + poolName := strings.ToLower(ordssrvs.Spec.PoolSettings[i].PoolName) - poolConfigName := ords.Name + "-" + poolConfigPreName + poolName - poolConfigVolume := volumeBuild(poolConfigName, "ConfigMap") + // /opt/oracle/sa/config/databases/POOL/ + poolConfigName := "config-database-pool-" + poolName + poolConfigVolume := volumeBuild(poolConfigName, poolConfigName, "ConfigMap") poolConfigVolumeMount := volumeMountBuild(poolConfigName, ordsSABase+"/config/databases/"+poolName+"/", true) + volumes = append(volumes, poolConfigVolume) + volumeMounts = append(volumeMounts, poolConfigVolumeMount) + + // PoolWalletSecret -> /opt/oracle/sa/config/databases/POOL/wallet/ + poolWalletVolumeName := "config-database-pool-wallet-" + poolName + poolWalletVolumePath := ordsSABase + "/config/databases/" + poolName + "/wallet/" + if ordssrvs.Spec.PoolSettings[i].PoolWalletSecret == nil { + poolWalletVolume := volumeBuild(poolWalletVolumeName, poolWalletVolumeName, "EmptyDir") + poolWalletVolumeMount := volumeMountBuild(poolWalletVolumeName, poolWalletVolumePath, false) + volumes = append(volumes, poolWalletVolume) + volumeMounts = append(volumeMounts, poolWalletVolumeMount) + } else { + poolWalletSecretName := ordssrvs.Spec.PoolSettings[i].PoolWalletSecret.SecretName + if !definedVolumes[poolWalletVolumeName] { + poolWalletVolume := volumeBuild(poolWalletVolumeName, poolWalletSecretName, "Secret") + volumes = append(volumes, poolWalletVolume) + definedVolumes[poolWalletVolumeName] = true + } + poolWalletVolumeMount := volumeMountBuild(poolWalletVolumeName, poolWalletVolumePath, true) + volumeMounts = append(volumeMounts, poolWalletVolumeMount) + } - volumes = append(volumes, poolWalletVolume, poolConfigVolume) - volumeMounts = append(volumeMounts, poolWalletVolumeMount, poolConfigVolumeMount) - - if ords.Spec.PoolSettings[i].DBWalletSecret != nil { - walletSecretName := ords.Spec.PoolSettings[i].DBWalletSecret.SecretName - if !definedWalletSecret[walletSecretName] { - // Only create the volume once - poolDBWalletVolume := volumeBuild(walletSecretName, "Secret") + // DBWalletSecret -> /opt/oracle/sa/config/databases/POOL/network/admin/ + if ordssrvs.Spec.PoolSettings[i].DBWalletSecret != nil { + dbWalletSecretName := ordssrvs.Spec.PoolSettings[i].DBWalletSecret.SecretName + volumeName := "config-database-pool-network-admin-dbwallet-" + poolName + if !definedVolumes[volumeName] { + poolDBWalletVolume := volumeBuild(volumeName, dbWalletSecretName, "Secret") volumes = append(volumes, poolDBWalletVolume) - definedWalletSecret[walletSecretName] = true + definedVolumes[volumeName] = true } - poolDBWalletVolumeMount := volumeMountBuild(walletSecretName, ordsSABase+"/config/databases/"+poolName+"/network/admin/", true) + poolDBWalletVolumeMount := volumeMountBuild(volumeName, ordsSABase+"/config/databases/"+poolName+"/network/admin/", true) volumeMounts = append(volumeMounts, poolDBWalletVolumeMount) } - if ords.Spec.PoolSettings[i].TNSAdminSecret != nil { - tnsSecretName := ords.Spec.PoolSettings[i].TNSAdminSecret.SecretName - if !definedTNSSecret[tnsSecretName] { - // Only create the volume once - poolTNSAdminVolume := volumeBuild(tnsSecretName, "Secret") - volumes = append(volumes, poolTNSAdminVolume) - definedTNSSecret[tnsSecretName] = true + // TNSAdminSecret -> /opt/oracle/sa/config/databases/POOL/network/admin/ + if ordssrvs.Spec.PoolSettings[i].TNSAdminSecret != nil { + if ordssrvs.Spec.PoolSettings[i].DBWalletSecret == nil { + tnsSecretName := ordssrvs.Spec.PoolSettings[i].TNSAdminSecret.SecretName + poolTNSAdminVolumeName := "config-database-pool-network-admin-" + poolName + if !definedVolumes[poolTNSAdminVolumeName] { + poolTNSAdminVolume := volumeBuild(poolTNSAdminVolumeName, tnsSecretName, "Secret") + volumes = append(volumes, poolTNSAdminVolume) + definedVolumes[poolTNSAdminVolumeName] = true + } + poolTNSAdminVolumeMount := volumeMountBuild(poolTNSAdminVolumeName, ordsSABase+"/config/databases/"+poolName+"/network/admin/", true) + volumeMounts = append(volumeMounts, poolTNSAdminVolumeMount) + } else { + logger.Info("Attribute TNSAdminSecret ignored, using DBWalletSecret for pool " + poolName) } - poolTNSAdminVolumeMount := volumeMountBuild(tnsSecretName, ordsSABase+"/config/databases/"+poolName+"/network/admin/", true) - volumeMounts = append(volumeMounts, poolTNSAdminVolumeMount) } + } return volumes, volumeMounts } @@ -834,7 +860,7 @@ func volumeMountBuild(name string, path string, readOnly bool) corev1.VolumeMoun } } -func volumeBuild(name string, source string, mode ...int32) corev1.Volume { +func volumeBuild(volumeName string, name string, source string, mode ...int32) corev1.Volume { defaultMode := int32(0660) if len(mode) > 0 { defaultMode = mode[0] @@ -842,7 +868,7 @@ func volumeBuild(name string, source string, mode ...int32) corev1.Volume { switch source { case "ConfigMap": return corev1.Volume{ - Name: name, + Name: volumeName, VolumeSource: corev1.VolumeSource{ ConfigMap: &corev1.ConfigMapVolumeSource{ DefaultMode: &defaultMode, @@ -854,7 +880,7 @@ func volumeBuild(name string, source string, mode ...int32) corev1.Volume { } case "Secret": return corev1.Volume{ - Name: name, + Name: volumeName, VolumeSource: corev1.VolumeSource{ Secret: &corev1.SecretVolumeSource{ SecretName: name, @@ -863,7 +889,7 @@ func volumeBuild(name string, source string, mode ...int32) corev1.Volume { } case "EmptyDir": return corev1.Volume{ - Name: name, + Name: volumeName, VolumeSource: corev1.VolumeSource{ EmptyDir: &corev1.EmptyDirVolumeSource{}, }, @@ -874,8 +900,8 @@ func volumeBuild(name string, source string, mode ...int32) corev1.Volume { } // Service -func (r *OrdsSrvsReconciler) ServiceDefine(ctx context.Context, ords *dbapi.OrdsSrvs, HTTPport int32, HTTPSport int32, MongoPort int32) *corev1.Service { - labels := getLabels(ords.Name) +func (r *OrdsSrvsReconciler) ServiceDefine(ctx context.Context, ordssrvs *dbapi.OrdsSrvs, HTTPport int32, HTTPSport int32, MongoPort int32) *corev1.Service { + labels := getLabels(ordssrvs.Name) servicePorts := []corev1.ServicePort{ { @@ -892,7 +918,7 @@ func (r *OrdsSrvsReconciler) ServiceDefine(ctx context.Context, ords *dbapi.Ords }, } - if ords.Spec.GlobalSettings.MongoEnabled { + if ordssrvs.Spec.GlobalSettings.MongoEnabled { mongoServicePort := corev1.ServicePort{ Name: serviceMongoPortName, Protocol: corev1.ProtocolTCP, @@ -902,7 +928,7 @@ func (r *OrdsSrvsReconciler) ServiceDefine(ctx context.Context, ords *dbapi.Ords servicePorts = append(servicePorts, mongoServicePort) } - objectMeta := objectMetaDefine(ords, ords.Name) + objectMeta := objectMetaDefine(ordssrvs, ordssrvs.Name) def := &corev1.Service{ ObjectMeta: objectMeta, Spec: corev1.ServiceSpec{ @@ -912,7 +938,7 @@ func (r *OrdsSrvsReconciler) ServiceDefine(ctx context.Context, ords *dbapi.Ords } // Set the ownerRef - if err := ctrl.SetControllerReference(ords, def, r.Scheme); err != nil { + if err := ctrl.SetControllerReference(ordssrvs, def, r.Scheme); err != nil { return nil } return def @@ -953,6 +979,41 @@ func addEnvVar(envVars []corev1.EnvVar, name string, value string) []corev1.EnvV return append(envVars, newEnvVar) } +func (r *OrdsSrvsReconciler) addSecretEnvVar(envVars []corev1.EnvVar, ordssrvs *dbapi.OrdsSrvs, envName string, secretName string, secretKey string, ctx context.Context) []corev1.EnvVar { + + logger := log.FromContext(ctx).WithName("addSecretEnvVar") + message := fmt.Sprintf("Setting Secret env variable '%s' from secret '%s', key '%s' ", envName, secretName, secretKey) + logger.Info(message) + + // check secret exists + var secret corev1.Secret + err := r.Client.Get(ctx, client.ObjectKey{Namespace: ordssrvs.Namespace, Name: secretName}, &secret) + if err != nil { + logger.Error(err, "Secret not found "+secretName) + return envVars + } + + // check key exists + if _, exists := secret.Data[secretKey]; !exists { + logger.Info("Secret key " + secretKey + " not found for secret " + secretName) + return envVars + } + + newEnvVar := corev1.EnvVar{ + Name: envName, + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: secretName, + }, + Key: secretKey, + }, + }, + } + + return append(envVars, newEnvVar) +} + // Sets environment variables in the containers func (r *OrdsSrvsReconciler) envDefine(ordssrvs *dbapi.OrdsSrvs, initContainer bool, ctx context.Context) []corev1.EnvVar { logger := log.FromContext(ctx).WithName("envDefine") @@ -964,6 +1025,13 @@ func (r *OrdsSrvsReconciler) envDefine(ordssrvs *dbapi.OrdsSrvs, initContainer b logger.Info("Setting ORDS_CONFIG to " + ORDS_CONFIG) envVars = addEnvVar(envVars, "ORDS_CONFIG", ORDS_CONFIG) + // adding info for private key + if passwordEncryption && initContainer { + passwordKey := ordssrvs.Spec.EncPrivKey.PasswordKey + logger.Info("Setting ENC_PRV_KEY env variable to " + passwordKey) + envVars = addEnvVar(envVars, "ENC_PRV_KEY", passwordKey) + } + // avoid Java warning about JAVA_TOOL_OPTIONS envVars = addEnvVar(envVars, "JAVA_TOOL_OPTIONS", "-Doracle.ml.version_check=false") @@ -974,33 +1042,48 @@ func (r *OrdsSrvsReconciler) envDefine(ordssrvs *dbapi.OrdsSrvs, initContainer b envVars = addEnvVar(envVars, "TNS_ADMIN", tnsAdmin) } - // passwords are set for the init container only + // init container only if initContainer { + envVars = addEnvVar(envVars, "download_apex", strconv.FormatBool(ordssrvs.Spec.GlobalSettings.APEXDownload)) envVars = addEnvVar(envVars, "download_url_apex", ordssrvs.Spec.GlobalSettings.APEXDownloadUrl) envVars = addEnvVar(envVars, "external_apex", APEXInstallationExternal) + // passwords are set for the init container only for i := 0; i < len(ordssrvs.Spec.PoolSettings); i++ { poolName := strings.ReplaceAll(strings.ToLower(ordssrvs.Spec.PoolSettings[i].PoolName), "-", "_") + logger.Info("Preparing env for pool " + poolName) // dbpassword - secretName := poolName + "_dbpassword" - secretValue := r.CommonDecryptWithPrivKey3(ordssrvs, ordssrvs.Spec.PoolSettings[i].DBSecret.SecretName, ordssrvs.Spec.PoolSettings[i].DBSecret.PasswordKey, ctx) - envVars = addEnvVar(envVars, secretName, secretValue) + envName := poolName + "_dbpassword" + // it can be provided by a wallet + if ordssrvs.Spec.PoolSettings[i].DBSecret.SecretName != "" { + secretName := ordssrvs.Spec.PoolSettings[i].DBSecret.SecretName + secretKey := ordssrvs.Spec.PoolSettings[i].DBSecret.PasswordKey + envVars = r.addSecretEnvVar(envVars, ordssrvs, envName, secretName, secretKey, ctx) + } // dbadminuserpassword if ordssrvs.Spec.PoolSettings[i].DBAdminUserSecret.SecretName != "" { + + // autoupgrade only if dbAdminPassword provided envVars = addEnvVar(envVars, poolName+"_autoupgrade_ords", strconv.FormatBool(ordssrvs.Spec.PoolSettings[i].AutoUpgradeORDS)) envVars = addEnvVar(envVars, poolName+"_autoupgrade_apex", strconv.FormatBool(ordssrvs.Spec.PoolSettings[i].AutoUpgradeAPEX)) - dbAdminUserPassword := r.CommonDecryptWithPrivKey3(ordssrvs, ordssrvs.Spec.PoolSettings[i].DBAdminUserSecret.SecretName, ordssrvs.Spec.PoolSettings[i].DBAdminUserSecret.PasswordKey, ctx) - envVars = addEnvVar(envVars, poolName+"_dbadminuserpassword", dbAdminUserPassword) + + envName := poolName + "_dbadminuserpassword" + secretName := ordssrvs.Spec.PoolSettings[i].DBAdminUserSecret.SecretName + secretKey := ordssrvs.Spec.PoolSettings[i].DBAdminUserSecret.PasswordKey + envVars = r.addSecretEnvVar(envVars, ordssrvs, envName, secretName, secretKey, ctx) } // dbcdbadminuserpassword if ordssrvs.Spec.PoolSettings[i].DBCDBAdminUserSecret.SecretName != "" { - dbCDBAdminUserPassword := r.CommonDecryptWithPrivKey3(ordssrvs, ordssrvs.Spec.PoolSettings[i].DBCDBAdminUserSecret.SecretName, ordssrvs.Spec.PoolSettings[i].DBCDBAdminUserSecret.PasswordKey, ctx) - envVars = addEnvVar(envVars, poolName+"_dbcdbadminuserpassword", dbCDBAdminUserPassword) + envName := poolName + "_dbcdbadminuserpassword" + secretName := ordssrvs.Spec.PoolSettings[i].DBCDBAdminUserSecret.SecretName + secretKey := ordssrvs.Spec.PoolSettings[i].DBCDBAdminUserSecret.PasswordKey + envVars = r.addSecretEnvVar(envVars, ordssrvs, envName, secretName, secretKey, ctx) } + } } @@ -1082,34 +1165,34 @@ func (r *OrdsSrvsReconciler) APEXInstallationPVCDefine(ctx context.Context, ords /************************************************* * Deletions **************************************************/ -func (r *OrdsSrvsReconciler) ConfigMapDelete(ctx context.Context, req ctrl.Request, ords *dbapi.OrdsSrvs, definedPools map[string]bool) (err error) { +func (r *OrdsSrvsReconciler) ConfigMapDelete(ctx context.Context, req ctrl.Request, ordssrvs *dbapi.OrdsSrvs, definedPools map[string]bool) (err error) { // Delete Undefined Pool ConfigMaps configMapList := &corev1.ConfigMapList{} if err := r.List(ctx, configMapList, client.InNamespace(req.Namespace), client.MatchingLabels(map[string]string{ controllerLabelKey: controllerLabelVal, - "app.kubernetes.io/instance": ords.Name}), + "app.kubernetes.io/instance": ordssrvs.Name}), ); err != nil { return err } for _, configMap := range configMapList.Items { - if configMap.Name == ordsGlobalConfig || configMap.Name == ordsInitScript || configMap.Name == ordsStartScript { + if configMap.Name == ordssrvsGlobalSettingsConfigMapName || configMap.Name == ordssrvsScriptsConfigMapName { continue } if _, exists := definedPools[configMap.Name]; !exists { if err := r.Delete(ctx, &configMap); err != nil { return err } - RestartPods = ords.Spec.ForceRestart - r.Recorder.Eventf(ords, corev1.EventTypeNormal, "Delete", "ConfigMap %s Deleted", configMap.Name) + RestartPods = ordssrvs.Spec.ForceRestart + r.Recorder.Eventf(ordssrvs, corev1.EventTypeNormal, "Delete", "ConfigMap %s Deleted", configMap.Name) } } return nil } -func (r *OrdsSrvsReconciler) WorkloadDelete(ctx context.Context, req ctrl.Request, ords *dbapi.OrdsSrvs, kind string) (err error) { +func (r *OrdsSrvsReconciler) WorkloadDelete(ctx context.Context, req ctrl.Request, ordssrvs *dbapi.OrdsSrvs, kind string) (err error) { logr := log.FromContext(ctx).WithName("WorkloadDelete") // Get Workloads @@ -1117,7 +1200,7 @@ func (r *OrdsSrvsReconciler) WorkloadDelete(ctx context.Context, req ctrl.Reques if err := r.List(ctx, deploymentList, client.InNamespace(req.Namespace), client.MatchingLabels(map[string]string{ controllerLabelKey: controllerLabelVal, - "app.kubernetes.io/instance": ords.Name}), + "app.kubernetes.io/instance": ordssrvs.Name}), ); err != nil { return err } @@ -1126,7 +1209,7 @@ func (r *OrdsSrvsReconciler) WorkloadDelete(ctx context.Context, req ctrl.Reques if err := r.List(ctx, statefulSetList, client.InNamespace(req.Namespace), client.MatchingLabels(map[string]string{ controllerLabelKey: controllerLabelVal, - "app.kubernetes.io/instance": ords.Name}), + "app.kubernetes.io/instance": ordssrvs.Name}), ); err != nil { return err } @@ -1135,7 +1218,7 @@ func (r *OrdsSrvsReconciler) WorkloadDelete(ctx context.Context, req ctrl.Reques if err := r.List(ctx, daemonSetList, client.InNamespace(req.Namespace), client.MatchingLabels(map[string]string{ controllerLabelKey: controllerLabelVal, - "app.kubernetes.io/instance": ords.Name}), + "app.kubernetes.io/instance": ordssrvs.Name}), ); err != nil { return err } @@ -1147,14 +1230,14 @@ func (r *OrdsSrvsReconciler) WorkloadDelete(ctx context.Context, req ctrl.Reques return err } logr.Info("Deleted: " + kind) - r.Recorder.Eventf(ords, corev1.EventTypeNormal, "Delete", "Workload %s Deleted", kind) + r.Recorder.Eventf(ordssrvs, corev1.EventTypeNormal, "Delete", "Workload %s Deleted", kind) } for _, deleteDeployment := range deploymentList.Items { if err := r.Delete(ctx, &deleteDeployment); err != nil { return err } logr.Info("Deleted: " + kind) - r.Recorder.Eventf(ords, corev1.EventTypeNormal, "Delete", "Workload %s Deleted", kind) + r.Recorder.Eventf(ordssrvs, corev1.EventTypeNormal, "Delete", "Workload %s Deleted", kind) } case "DaemonSet": for _, deleteDeployment := range deploymentList.Items { @@ -1162,14 +1245,14 @@ func (r *OrdsSrvsReconciler) WorkloadDelete(ctx context.Context, req ctrl.Reques return err } logr.Info("Deleted: " + kind) - r.Recorder.Eventf(ords, corev1.EventTypeNormal, "Delete", "Workload %s Deleted", kind) + r.Recorder.Eventf(ordssrvs, corev1.EventTypeNormal, "Delete", "Workload %s Deleted", kind) } for _, deleteStatefulSet := range statefulSetList.Items { if err := r.Delete(ctx, &deleteStatefulSet); err != nil { return err } logr.Info("Deleted StatefulSet: " + deleteStatefulSet.Name) - r.Recorder.Eventf(ords, corev1.EventTypeNormal, "Delete", "Workload %s Deleted", kind) + r.Recorder.Eventf(ordssrvs, corev1.EventTypeNormal, "Delete", "Workload %s Deleted", kind) } default: for _, deleteStatefulSet := range statefulSetList.Items { @@ -1177,14 +1260,14 @@ func (r *OrdsSrvsReconciler) WorkloadDelete(ctx context.Context, req ctrl.Reques return err } logr.Info("Deleted: " + kind) - r.Recorder.Eventf(ords, corev1.EventTypeNormal, "Delete", "Workload %s Deleted", kind) + r.Recorder.Eventf(ordssrvs, corev1.EventTypeNormal, "Delete", "Workload %s Deleted", kind) } for _, deleteDaemonSet := range daemonSetList.Items { if err := r.Delete(ctx, &deleteDaemonSet); err != nil { return err } logr.Info("Deleted: " + kind) - r.Recorder.Eventf(ords, corev1.EventTypeNormal, "Delete", "Workload %s Deleted", kind) + r.Recorder.Eventf(ordssrvs, corev1.EventTypeNormal, "Delete", "Workload %s Deleted", kind) } } return nil @@ -1217,76 +1300,3 @@ func generateSpecHash(spec interface{}) string { return hashString } - -func CommonDecryptWithPrivKey(Key string, Buffer string) (string, error) { - - Debug := 0 - block, _ := pem.Decode([]byte(Key)) - pkcs8PrivateKey, err := x509.ParsePKCS8PrivateKey(block.Bytes) - if err != nil { - fmt.Printf("Failed to parse private key %s \n", err.Error()) - return "", err - } - if Debug == 1 { - fmt.Printf("======================================\n") - fmt.Printf("%s\n", Key) - fmt.Printf("======================================\n") - } - - encString64, err := base64.StdEncoding.DecodeString(string(Buffer)) - if err != nil { - fmt.Printf("Failed to decode encrypted string to base64: %s\n", err.Error()) - return "", err - } - - if Debug == 1 { - fmt.Printf("======================================\n") - fmt.Printf("%s\n", encString64) - fmt.Printf("======================================\n") - } - - decryptedB, err := rsa.DecryptOAEP(sha256.New(), rand.Reader, pkcs8PrivateKey.(*rsa.PrivateKey), encString64, nil) - if err != nil { - fmt.Printf("Failed to decrypt string %s\n", err.Error()) - return "", err - } - if Debug == 1 { - fmt.Printf("[%s]\n", string(decryptedB)) - } - return strings.TrimSpace(string(decryptedB)), err - -} - -func (r *OrdsSrvsReconciler) CommonDecryptWithPrivKey3(ords *dbapi.OrdsSrvs, sname string, skey string, ctx context.Context) string { - logr := log.FromContext(ctx).WithName("CommonDecryptWithPrivKey2") - secret_par := &corev1.Secret{} - fmt.Printf("sname: %s\n", sname) - fmt.Printf("skey: %s\n", skey) - err := r.Get(ctx, types.NamespacedName{Name: sname, Namespace: ords.Namespace}, secret_par) - if err != nil { - logr.Error(err, "Cannot read secret"+sname) - return "" - } - encVal := string(secret_par.Data[skey]) - encVal = strings.TrimSpace(encVal) - - secret_key := &corev1.Secret{} - /* get private key */ - if err := r.Get(ctx, types.NamespacedName{Name: ords.Spec.EncPrivKey.SecretName, - Namespace: ords.Namespace}, secret_key); err != nil { - logr.Error(err, "Cannot get privte key") - return "" - } - PrvKeyVal := string(secret_key.Data[ords.Spec.EncPrivKey.PasswordKey]) - PrvKeyVal = strings.TrimSpace(PrvKeyVal) - - decVal, err := CommonDecryptWithPrivKey(PrvKeyVal, encVal) - if err != nil { - logr.Error(err, "Fail to decrypt secret") - return "" - } - - logr.Info("Password decryption completed") - - return decVal -} diff --git a/controllers/database/ordssrvs_ordsconfig.go b/controllers/database/ordssrvs_ordsconfig.go index 5cd29536..98ec70a1 100644 --- a/controllers/database/ordssrvs_ordsconfig.go +++ b/controllers/database/ordssrvs_ordsconfig.go @@ -52,91 +52,88 @@ import ( ctrllog "sigs.k8s.io/controller-runtime/pkg/log" ) -func (r *OrdsSrvsReconciler) ConfigMapDefine(ctx context.Context, ords *dbapi.OrdsSrvs, configMapName string, poolIndex int) *corev1.ConfigMap { +func readScript(ctx context.Context, filePath string) string { + log := ctrllog.FromContext(ctx).WithName("readScript") - log := ctrllog.FromContext(ctx).WithName("ConfigMapDefine") + // Read the file from controller's filesystem + scriptData, err := os.ReadFile(filePath) + if err != nil { + log.Error(err, "Error reading "+filePath) + return "error" + } + + return string(scriptData) +} + +func (r *OrdsSrvsReconciler) ConfigMapDefine(ctx context.Context, ordssrvs *dbapi.OrdsSrvs, configMapName string, poolIndex int) *corev1.ConfigMap { + + //log := ctrllog.FromContext(ctx).WithName("ConfigMapDefine") var defData map[string]string switch configMapName { - case ords.Name + "-init-script": - // Read the file from controller's filesystem - filePath := "/ords_init.sh" - scriptData, err := os.ReadFile(filePath) - if err != nil { - log.Error(err, "Error reading /ords_init.sh") - return nil - } - log.Info("adding ords_init.sh") - defData = map[string]string{ - "ords_init.sh": string(scriptData)} - case ords.Name + "-start-script": - // Read the file from controller's filesystem - filePath := "/ords_start.sh" - scriptData, err := os.ReadFile(filePath) - if err != nil { - log.Error(err, "Error reading /ords_start.sh") - return nil - } - log.Info("adding ords_start.sh") - defData = map[string]string{ - "ords_start.sh": string(scriptData)} - case ords.Name + "-" + globalConfigMapName: + case ordssrvsScriptsConfigMapName: + defData = make(map[string]string) + defData["ords_init.sh"] = readScript(ctx, "/ordssrvs/ords_init.sh") + defData["ords_start.sh"] = readScript(ctx, "/ordssrvs/ords_start.sh") + defData["RSADecryptOAEP.java"] = readScript(ctx, "/ordssrvs/RSADecryptOAEP.java") + case ordssrvsGlobalSettingsConfigMapName: // GlobalConfigMap var defStandaloneAccessLog string - if ords.Spec.GlobalSettings.EnableStandaloneAccessLog { + if ordssrvs.Spec.GlobalSettings.EnableStandaloneAccessLog { defStandaloneAccessLog = ` ` + ordsSABase + `/log/global` + "\n" } var defMongoAccessLog string - if ords.Spec.GlobalSettings.EnableMongoAccessLog { + if ordssrvs.Spec.GlobalSettings.EnableMongoAccessLog { defMongoAccessLog = ` ` + ordsSABase + `/log/global` + "\n" } var defCert string - if ords.Spec.GlobalSettings.CertSecret != nil { - defCert = ` ` + ordsSABase + `/config/certficate/` + ords.Spec.GlobalSettings.CertSecret.Certificate + `` + "\n" + - ` ` + ordsSABase + `/config/certficate/` + ords.Spec.GlobalSettings.CertSecret.CertificateKey + `` + "\n" + if ordssrvs.Spec.GlobalSettings.CertSecret != nil { + defCert = ` ` + ordsSABase + `/config/certficate/` + ordssrvs.Spec.GlobalSettings.CertSecret.Certificate + `` + "\n" + + ` ` + ordsSABase + `/config/certficate/` + ordssrvs.Spec.GlobalSettings.CertSecret.CertificateKey + `` + "\n" } defData = map[string]string{ "settings.xml": fmt.Sprint(`` + "\n" + `` + "\n" + `` + "\n" + - conditionalEntry("cache.metadata.graphql.expireAfterAccess", ords.Spec.GlobalSettings.CacheMetadataGraphQLExpireAfterAccess) + - conditionalEntry("cache.metadata.jwks.enabled", ords.Spec.GlobalSettings.CacheMetadataJWKSEnabled) + - conditionalEntry("cache.metadata.jwks.initialCapacity", ords.Spec.GlobalSettings.CacheMetadataJWKSInitialCapacity) + - conditionalEntry("cache.metadata.jwks.maximumSize", ords.Spec.GlobalSettings.CacheMetadataJWKSMaximumSize) + - conditionalEntry("cache.metadata.jwks.expireAfterAccess", ords.Spec.GlobalSettings.CacheMetadataJWKSExpireAfterAccess) + - conditionalEntry("cache.metadata.jwks.expireAfterWrite", ords.Spec.GlobalSettings.CacheMetadataJWKSExpireAfterWrite) + - conditionalEntry("database.api.management.services.disabled", ords.Spec.GlobalSettings.DatabaseAPIManagementServicesDisabled) + - conditionalEntry("db.invalidPoolTimeout", ords.Spec.GlobalSettings.DBInvalidPoolTimeout) + - conditionalEntry("feature.graphql.max.nesting.depth", ords.Spec.GlobalSettings.FeatureGraphQLMaxNestingDepth) + - conditionalEntry("request.traceHeaderName", ords.Spec.GlobalSettings.RequestTraceHeaderName) + - conditionalEntry("security.credentials.attempts", ords.Spec.GlobalSettings.SecurityCredentialsAttempts) + - conditionalEntry("security.credentials.lock.time", ords.Spec.GlobalSettings.SecurityCredentialsLockTime) + - conditionalEntry("standalone.context.path", ords.Spec.GlobalSettings.StandaloneContextPath) + - conditionalEntry("standalone.http.port", ords.Spec.GlobalSettings.StandaloneHTTPPort) + - conditionalEntry("standalone.https.host", ords.Spec.GlobalSettings.StandaloneHTTPSHost) + - conditionalEntry("standalone.https.port", ords.Spec.GlobalSettings.StandaloneHTTPSPort) + - conditionalEntry("standalone.stop.timeout", ords.Spec.GlobalSettings.StandaloneStopTimeout) + - conditionalEntry("cache.metadata.timeout", ords.Spec.GlobalSettings.CacheMetadataTimeout) + - conditionalEntry("cache.metadata.enabled", ords.Spec.GlobalSettings.CacheMetadataEnabled) + - conditionalEntry("database.api.enabled", ords.Spec.GlobalSettings.DatabaseAPIEnabled) + - conditionalEntry("debug.printDebugToScreen", ords.Spec.GlobalSettings.DebugPrintDebugToScreen) + - conditionalEntry("error.responseFormat", ords.Spec.GlobalSettings.ErrorResponseFormat) + - conditionalEntry("icap.port", ords.Spec.GlobalSettings.ICAPPort) + - conditionalEntry("icap.secure.port", ords.Spec.GlobalSettings.ICAPSecurePort) + - conditionalEntry("icap.server", ords.Spec.GlobalSettings.ICAPServer) + - conditionalEntry("log.procedure", ords.Spec.GlobalSettings.LogProcedure) + - conditionalEntry("mongo.enabled", ords.Spec.GlobalSettings.MongoEnabled) + - conditionalEntry("mongo.port", ords.Spec.GlobalSettings.MongoPort) + - conditionalEntry("mongo.idle.timeout", ords.Spec.GlobalSettings.MongoIdleTimeout) + - conditionalEntry("mongo.op.timeout", ords.Spec.GlobalSettings.MongoOpTimeout) + - conditionalEntry("security.disableDefaultExclusionList", ords.Spec.GlobalSettings.SecurityDisableDefaultExclusionList) + - conditionalEntry("security.exclusionList", ords.Spec.GlobalSettings.SecurityExclusionList) + - conditionalEntry("security.inclusionList", ords.Spec.GlobalSettings.SecurityInclusionList) + - conditionalEntry("security.maxEntries", ords.Spec.GlobalSettings.SecurityMaxEntries) + - conditionalEntry("security.verifySSL", ords.Spec.GlobalSettings.SecurityVerifySSL) + - conditionalEntry("security.httpsHeaderCheck", ords.Spec.GlobalSettings.SecurityHTTPSHeaderCheck) + - conditionalEntry("security.forceHTTPS", ords.Spec.GlobalSettings.SecurityForceHTTPS) + - conditionalEntry("externalSessionTrustedOrigins", ords.Spec.GlobalSettings.SecuirtyExternalSessionTrustedOrigins) + + conditionalEntry("cache.metadata.graphql.expireAfterAccess", ordssrvs.Spec.GlobalSettings.CacheMetadataGraphQLExpireAfterAccess) + + conditionalEntry("cache.metadata.jwks.enabled", ordssrvs.Spec.GlobalSettings.CacheMetadataJWKSEnabled) + + conditionalEntry("cache.metadata.jwks.initialCapacity", ordssrvs.Spec.GlobalSettings.CacheMetadataJWKSInitialCapacity) + + conditionalEntry("cache.metadata.jwks.maximumSize", ordssrvs.Spec.GlobalSettings.CacheMetadataJWKSMaximumSize) + + conditionalEntry("cache.metadata.jwks.expireAfterAccess", ordssrvs.Spec.GlobalSettings.CacheMetadataJWKSExpireAfterAccess) + + conditionalEntry("cache.metadata.jwks.expireAfterWrite", ordssrvs.Spec.GlobalSettings.CacheMetadataJWKSExpireAfterWrite) + + conditionalEntry("database.api.management.services.disabled", ordssrvs.Spec.GlobalSettings.DatabaseAPIManagementServicesDisabled) + + conditionalEntry("db.invalidPoolTimeout", ordssrvs.Spec.GlobalSettings.DBInvalidPoolTimeout) + + conditionalEntry("feature.graphql.max.nesting.depth", ordssrvs.Spec.GlobalSettings.FeatureGraphQLMaxNestingDepth) + + conditionalEntry("request.traceHeaderName", ordssrvs.Spec.GlobalSettings.RequestTraceHeaderName) + + conditionalEntry("security.credentials.attempts", ordssrvs.Spec.GlobalSettings.SecurityCredentialsAttempts) + + conditionalEntry("security.credentials.lock.time", ordssrvs.Spec.GlobalSettings.SecurityCredentialsLockTime) + + conditionalEntry("standalone.context.path", ordssrvs.Spec.GlobalSettings.StandaloneContextPath) + + conditionalEntry("standalone.http.port", ordssrvs.Spec.GlobalSettings.StandaloneHTTPPort) + + conditionalEntry("standalone.https.host", ordssrvs.Spec.GlobalSettings.StandaloneHTTPSHost) + + conditionalEntry("standalone.https.port", ordssrvs.Spec.GlobalSettings.StandaloneHTTPSPort) + + conditionalEntry("standalone.stop.timeout", ordssrvs.Spec.GlobalSettings.StandaloneStopTimeout) + + conditionalEntry("cache.metadata.timeout", ordssrvs.Spec.GlobalSettings.CacheMetadataTimeout) + + conditionalEntry("cache.metadata.enabled", ordssrvs.Spec.GlobalSettings.CacheMetadataEnabled) + + conditionalEntry("database.api.enabled", ordssrvs.Spec.GlobalSettings.DatabaseAPIEnabled) + + conditionalEntry("instance.api.enabled", ordssrvs.Spec.GlobalSettings.InstanceAPIEnabled) + + conditionalEntry("debug.printDebugToScreen", ordssrvs.Spec.GlobalSettings.DebugPrintDebugToScreen) + + conditionalEntry("error.responseFormat", ordssrvs.Spec.GlobalSettings.ErrorResponseFormat) + + conditionalEntry("icap.port", ordssrvs.Spec.GlobalSettings.ICAPPort) + + conditionalEntry("icap.secure.port", ordssrvs.Spec.GlobalSettings.ICAPSecurePort) + + conditionalEntry("icap.server", ordssrvs.Spec.GlobalSettings.ICAPServer) + + conditionalEntry("log.procedure", ordssrvs.Spec.GlobalSettings.LogProcedure) + + conditionalEntry("mongo.enabled", ordssrvs.Spec.GlobalSettings.MongoEnabled) + + conditionalEntry("mongo.port", ordssrvs.Spec.GlobalSettings.MongoPort) + + conditionalEntry("mongo.idle.timeout", ordssrvs.Spec.GlobalSettings.MongoIdleTimeout) + + conditionalEntry("mongo.op.timeout", ordssrvs.Spec.GlobalSettings.MongoOpTimeout) + + conditionalEntry("security.disableDefaultExclusionList", ordssrvs.Spec.GlobalSettings.SecurityDisableDefaultExclusionList) + + conditionalEntry("security.exclusionList", ordssrvs.Spec.GlobalSettings.SecurityExclusionList) + + conditionalEntry("security.inclusionList", ordssrvs.Spec.GlobalSettings.SecurityInclusionList) + + conditionalEntry("security.maxEntries", ordssrvs.Spec.GlobalSettings.SecurityMaxEntries) + + conditionalEntry("security.verifySSL", ordssrvs.Spec.GlobalSettings.SecurityVerifySSL) + + conditionalEntry("security.httpsHeaderCheck", ordssrvs.Spec.GlobalSettings.SecurityHTTPSHeaderCheck) + + conditionalEntry("security.forceHTTPS", ordssrvs.Spec.GlobalSettings.SecurityForceHTTPS) + + conditionalEntry("externalSessionTrustedOrigins", ordssrvs.Spec.GlobalSettings.SecuirtyExternalSessionTrustedOrigins) + ` ` + ordsSABase + `/config/global/doc_root/` + "\n" + // Dynamic defStandaloneAccessLog + @@ -159,11 +156,11 @@ func (r *OrdsSrvsReconciler) ConfigMapDefine(ctx context.Context, ords *dbapi.Or } default: // PoolConfigMap - poolName := strings.ToLower(ords.Spec.PoolSettings[poolIndex].PoolName) + poolName := strings.ToLower(ordssrvs.Spec.PoolSettings[poolIndex].PoolName) var defDBNetworkPath string - if ords.Spec.PoolSettings[poolIndex].DBWalletSecret != nil { - defDBNetworkPath = ` ` + ordsSABase + `/config/databases/` + poolName + `/network/admin/` + ords.Spec.PoolSettings[poolIndex].DBWalletSecret.WalletName + `` + "\n" + - conditionalEntry("db.wallet.zip.service", strings.ToUpper(ords.Spec.PoolSettings[poolIndex].DBWalletZipService)) + "\n" + if ordssrvs.Spec.PoolSettings[poolIndex].DBWalletSecret != nil { + defDBNetworkPath = ` ` + ordsSABase + `/config/databases/` + poolName + `/network/admin/` + ordssrvs.Spec.PoolSettings[poolIndex].DBWalletSecret.WalletName + `` + "\n" + + conditionalEntry("db.wallet.zip.service", strings.ToUpper(ordssrvs.Spec.PoolSettings[poolIndex].DBWalletZipService)) + "\n" } else { defDBNetworkPath = ` ` + ordsSABase + `/config/databases/` + poolName + `/network/admin/` + "\n" } @@ -171,56 +168,56 @@ func (r *OrdsSrvsReconciler) ConfigMapDefine(ctx context.Context, ords *dbapi.Or "pool.xml": fmt.Sprint(`` + "\n" + `` + "\n" + `` + "\n" + - ` ` + ords.Spec.PoolSettings[poolIndex].DBUsername + `` + "\n" + - conditionalEntry("db.adminUser", ords.Spec.PoolSettings[poolIndex].DBAdminUser) + - conditionalEntry("db.cdb.adminUser", ords.Spec.PoolSettings[poolIndex].DBCDBAdminUser) + - conditionalEntry("apex.security.administrator.roles", ords.Spec.PoolSettings[poolIndex].ApexSecurityAdministratorRoles) + - conditionalEntry("apex.security.user.roles", ords.Spec.PoolSettings[poolIndex].ApexSecurityUserRoles) + - conditionalEntry("db.credentialsSource", ords.Spec.PoolSettings[poolIndex].DBCredentialsSource) + - conditionalEntry("db.poolDestroyTimeout", ords.Spec.PoolSettings[poolIndex].DBPoolDestroyTimeout) + - conditionalEntry("debug.trackResources", ords.Spec.PoolSettings[poolIndex].DebugTrackResources) + - conditionalEntry("feature.openservicebroker.exclude", ords.Spec.PoolSettings[poolIndex].FeatureOpenservicebrokerExclude) + - conditionalEntry("feature.sdw", ords.Spec.PoolSettings[poolIndex].FeatureSDW) + - conditionalEntry("http.cookie.filter", ords.Spec.PoolSettings[poolIndex].HttpCookieFilter) + - conditionalEntry("jdbc.auth.admin.role", ords.Spec.PoolSettings[poolIndex].JDBCAuthAdminRole) + - conditionalEntry("jdbc.cleanup.mode", ords.Spec.PoolSettings[poolIndex].JDBCCleanupMode) + - conditionalEntry("owa.trace.sql", ords.Spec.PoolSettings[poolIndex].OwaTraceSql) + - conditionalEntry("plsql.gateway.mode", ords.Spec.PoolSettings[poolIndex].PlsqlGatewayMode) + - conditionalEntry("security.jwt.profile.enabled", ords.Spec.PoolSettings[poolIndex].SecurityJWTProfileEnabled) + - conditionalEntry("security.jwks.size", ords.Spec.PoolSettings[poolIndex].SecurityJWKSSize) + - conditionalEntry("security.jwks.connection.timeout", ords.Spec.PoolSettings[poolIndex].SecurityJWKSConnectionTimeout) + - conditionalEntry("security.jwks.read.timeout", ords.Spec.PoolSettings[poolIndex].SecurityJWKSReadTimeout) + - conditionalEntry("security.jwks.refresh.interval", ords.Spec.PoolSettings[poolIndex].SecurityJWKSRefreshInterval) + - conditionalEntry("security.jwt.allowed.skew", ords.Spec.PoolSettings[poolIndex].SecurityJWTAllowedSkew) + - conditionalEntry("security.jwt.allowed.age", ords.Spec.PoolSettings[poolIndex].SecurityJWTAllowedAge) + - conditionalEntry("db.connectionType", ords.Spec.PoolSettings[poolIndex].DBConnectionType) + - conditionalEntry("db.customURL", ords.Spec.PoolSettings[poolIndex].DBCustomURL) + - conditionalEntry("db.hostname", ords.Spec.PoolSettings[poolIndex].DBHostname) + - conditionalEntry("db.port", ords.Spec.PoolSettings[poolIndex].DBPort) + - conditionalEntry("db.servicename", ords.Spec.PoolSettings[poolIndex].DBServicename) + - conditionalEntry("db.sid", ords.Spec.PoolSettings[poolIndex].DBSid) + - conditionalEntry("db.tnsAliasName", ords.Spec.PoolSettings[poolIndex].DBTnsAliasName) + - conditionalEntry("jdbc.DriverType", ords.Spec.PoolSettings[poolIndex].JDBCDriverType) + - conditionalEntry("jdbc.InactivityTimeout", ords.Spec.PoolSettings[poolIndex].JDBCInactivityTimeout) + - conditionalEntry("jdbc.InitialLimit", ords.Spec.PoolSettings[poolIndex].JDBCInitialLimit) + - conditionalEntry("jdbc.MaxConnectionReuseCount", ords.Spec.PoolSettings[poolIndex].JDBCMaxConnectionReuseCount) + - conditionalEntry("jdbc.MaxLimit", ords.Spec.PoolSettings[poolIndex].JDBCMaxLimit) + - conditionalEntry("jdbc.auth.enabled", ords.Spec.PoolSettings[poolIndex].JDBCAuthEnabled) + - conditionalEntry("jdbc.MaxStatementsLimit", ords.Spec.PoolSettings[poolIndex].JDBCMaxStatementsLimit) + - conditionalEntry("jdbc.MinLimit", ords.Spec.PoolSettings[poolIndex].JDBCMinLimit) + - conditionalEntry("jdbc.statementTimeout", ords.Spec.PoolSettings[poolIndex].JDBCStatementTimeout) + - conditionalEntry("jdbc.MaxConnectionReuseTime", ords.Spec.PoolSettings[poolIndex].JDBCMaxConnectionReuseTime) + - conditionalEntry("jdbc.SecondsToTrustIdleConnection", ords.Spec.PoolSettings[poolIndex].JDBCSecondsToTrustIdleConnection) + - conditionalEntry("misc.defaultPage", ords.Spec.PoolSettings[poolIndex].MiscDefaultPage) + - conditionalEntry("misc.pagination.maxRows", ords.Spec.PoolSettings[poolIndex].MiscPaginationMaxRows) + - conditionalEntry("procedure.postProcess", ords.Spec.PoolSettings[poolIndex].ProcedurePostProcess) + - conditionalEntry("procedure.preProcess", ords.Spec.PoolSettings[poolIndex].ProcedurePreProcess) + - conditionalEntry("procedure.rest.preHook", ords.Spec.PoolSettings[poolIndex].ProcedureRestPreHook) + - conditionalEntry("security.requestAuthenticationFunction", ords.Spec.PoolSettings[poolIndex].SecurityRequestAuthenticationFunction) + - conditionalEntry("security.requestValidationFunction", ords.Spec.PoolSettings[poolIndex].SecurityRequestValidationFunction) + - conditionalEntry("soda.defaultLimit", ords.Spec.PoolSettings[poolIndex].SODADefaultLimit) + - conditionalEntry("soda.maxLimit", ords.Spec.PoolSettings[poolIndex].SODAMaxLimit) + - conditionalEntry("restEnabledSql.active", ords.Spec.PoolSettings[poolIndex].RestEnabledSqlActive) + + ` ` + ordssrvs.Spec.PoolSettings[poolIndex].DBUsername + `` + "\n" + + conditionalEntry("db.adminUser", ordssrvs.Spec.PoolSettings[poolIndex].DBAdminUser) + + conditionalEntry("db.cdb.adminUser", ordssrvs.Spec.PoolSettings[poolIndex].DBCDBAdminUser) + + conditionalEntry("apex.security.administrator.roles", ordssrvs.Spec.PoolSettings[poolIndex].ApexSecurityAdministratorRoles) + + conditionalEntry("apex.security.user.roles", ordssrvs.Spec.PoolSettings[poolIndex].ApexSecurityUserRoles) + + conditionalEntry("db.credentialsSource", ordssrvs.Spec.PoolSettings[poolIndex].DBCredentialsSource) + + conditionalEntry("db.poolDestroyTimeout", ordssrvs.Spec.PoolSettings[poolIndex].DBPoolDestroyTimeout) + + conditionalEntry("debug.trackResources", ordssrvs.Spec.PoolSettings[poolIndex].DebugTrackResources) + + conditionalEntry("feature.openservicebroker.exclude", ordssrvs.Spec.PoolSettings[poolIndex].FeatureOpenservicebrokerExclude) + + conditionalEntry("feature.sdw", ordssrvs.Spec.PoolSettings[poolIndex].FeatureSDW) + + conditionalEntry("http.cookie.filter", ordssrvs.Spec.PoolSettings[poolIndex].HttpCookieFilter) + + conditionalEntry("jdbc.auth.admin.role", ordssrvs.Spec.PoolSettings[poolIndex].JDBCAuthAdminRole) + + conditionalEntry("jdbc.cleanup.mode", ordssrvs.Spec.PoolSettings[poolIndex].JDBCCleanupMode) + + conditionalEntry("owa.trace.sql", ordssrvs.Spec.PoolSettings[poolIndex].OwaTraceSql) + + conditionalEntry("plsql.gateway.mode", ordssrvs.Spec.PoolSettings[poolIndex].PlsqlGatewayMode) + + conditionalEntry("security.jwt.profile.enabled", ordssrvs.Spec.PoolSettings[poolIndex].SecurityJWTProfileEnabled) + + conditionalEntry("security.jwks.size", ordssrvs.Spec.PoolSettings[poolIndex].SecurityJWKSSize) + + conditionalEntry("security.jwks.connection.timeout", ordssrvs.Spec.PoolSettings[poolIndex].SecurityJWKSConnectionTimeout) + + conditionalEntry("security.jwks.read.timeout", ordssrvs.Spec.PoolSettings[poolIndex].SecurityJWKSReadTimeout) + + conditionalEntry("security.jwks.refresh.interval", ordssrvs.Spec.PoolSettings[poolIndex].SecurityJWKSRefreshInterval) + + conditionalEntry("security.jwt.allowed.skew", ordssrvs.Spec.PoolSettings[poolIndex].SecurityJWTAllowedSkew) + + conditionalEntry("security.jwt.allowed.age", ordssrvs.Spec.PoolSettings[poolIndex].SecurityJWTAllowedAge) + + conditionalEntry("db.connectionType", ordssrvs.Spec.PoolSettings[poolIndex].DBConnectionType) + + conditionalEntry("db.customURL", ordssrvs.Spec.PoolSettings[poolIndex].DBCustomURL) + + conditionalEntry("db.hostname", ordssrvs.Spec.PoolSettings[poolIndex].DBHostname) + + conditionalEntry("db.port", ordssrvs.Spec.PoolSettings[poolIndex].DBPort) + + conditionalEntry("db.servicename", ordssrvs.Spec.PoolSettings[poolIndex].DBServicename) + + conditionalEntry("db.sid", ordssrvs.Spec.PoolSettings[poolIndex].DBSid) + + conditionalEntry("db.tnsAliasName", ordssrvs.Spec.PoolSettings[poolIndex].DBTnsAliasName) + + conditionalEntry("jdbc.DriverType", ordssrvs.Spec.PoolSettings[poolIndex].JDBCDriverType) + + conditionalEntry("jdbc.InactivityTimeout", ordssrvs.Spec.PoolSettings[poolIndex].JDBCInactivityTimeout) + + conditionalEntry("jdbc.InitialLimit", ordssrvs.Spec.PoolSettings[poolIndex].JDBCInitialLimit) + + conditionalEntry("jdbc.MaxConnectionReuseCount", ordssrvs.Spec.PoolSettings[poolIndex].JDBCMaxConnectionReuseCount) + + conditionalEntry("jdbc.MaxLimit", ordssrvs.Spec.PoolSettings[poolIndex].JDBCMaxLimit) + + conditionalEntry("jdbc.auth.enabled", ordssrvs.Spec.PoolSettings[poolIndex].JDBCAuthEnabled) + + conditionalEntry("jdbc.MaxStatementsLimit", ordssrvs.Spec.PoolSettings[poolIndex].JDBCMaxStatementsLimit) + + conditionalEntry("jdbc.MinLimit", ordssrvs.Spec.PoolSettings[poolIndex].JDBCMinLimit) + + conditionalEntry("jdbc.statementTimeout", ordssrvs.Spec.PoolSettings[poolIndex].JDBCStatementTimeout) + + conditionalEntry("jdbc.MaxConnectionReuseTime", ordssrvs.Spec.PoolSettings[poolIndex].JDBCMaxConnectionReuseTime) + + conditionalEntry("jdbc.SecondsToTrustIdleConnection", ordssrvs.Spec.PoolSettings[poolIndex].JDBCSecondsToTrustIdleConnection) + + conditionalEntry("misc.defaultPage", ordssrvs.Spec.PoolSettings[poolIndex].MiscDefaultPage) + + conditionalEntry("misc.pagination.maxRows", ordssrvs.Spec.PoolSettings[poolIndex].MiscPaginationMaxRows) + + conditionalEntry("procedure.postProcess", ordssrvs.Spec.PoolSettings[poolIndex].ProcedurePostProcess) + + conditionalEntry("procedure.preProcess", ordssrvs.Spec.PoolSettings[poolIndex].ProcedurePreProcess) + + conditionalEntry("procedure.rest.preHook", ordssrvs.Spec.PoolSettings[poolIndex].ProcedureRestPreHook) + + conditionalEntry("security.requestAuthenticationFunction", ordssrvs.Spec.PoolSettings[poolIndex].SecurityRequestAuthenticationFunction) + + conditionalEntry("security.requestValidationFunction", ordssrvs.Spec.PoolSettings[poolIndex].SecurityRequestValidationFunction) + + conditionalEntry("soda.defaultLimit", ordssrvs.Spec.PoolSettings[poolIndex].SODADefaultLimit) + + conditionalEntry("soda.maxLimit", ordssrvs.Spec.PoolSettings[poolIndex].SODAMaxLimit) + + conditionalEntry("restEnabledSql.active", ordssrvs.Spec.PoolSettings[poolIndex].RestEnabledSqlActive) + defDBNetworkPath + // Disabled (but not forgotten) // conditionalEntry("autoupgrade.api.aulocation", ords.Spec.PoolSettings[poolIndex].AutoupgradeAPIAulocation) + @@ -232,7 +229,7 @@ func (r *OrdsSrvsReconciler) ConfigMapDefine(ctx context.Context, ords *dbapi.Or } } - objectMeta := objectMetaDefine(ords, configMapName) + objectMeta := objectMetaDefine(ordssrvs, configMapName) def := &corev1.ConfigMap{ TypeMeta: metav1.TypeMeta{ Kind: "ConfigMap", @@ -243,7 +240,7 @@ func (r *OrdsSrvsReconciler) ConfigMapDefine(ctx context.Context, ords *dbapi.Or } // Set the ownerRef - if err := ctrl.SetControllerReference(ords, def, r.Scheme); err != nil { + if err := ctrl.SetControllerReference(ordssrvs, def, r.Scheme); err != nil { return nil } return def diff --git a/docs/ordsservices/README.md b/docs/ordsservices/README.md index d012d4ef..786a3755 100644 --- a/docs/ordsservices/README.md +++ b/docs/ordsservices/README.md @@ -111,12 +111,17 @@ The "Conclusion" section of each example highlights specific settings to enable * [Autonomous Database without the OraOperator](./examples/adb.md) * [Oracle API for MongoDB Support](./examples/mongo_api.md) * [ORDS and APEX database schemas automatic installation/upgrade](./autoupgrade.md) +* [External tnsnames.ora and Database credentials in an Oracle Wallet](./examples/resources.md) Running through all examples in the same Kubernetes cluster illustrates the ability to run multiple ORDS instances with a variety of different configurations. ## Change Log +### Development + + + ### Version 2.0 - ORDS image 25.1+ diff --git a/docs/ordsservices/examples/resources.md b/docs/ordsservices/examples/resources.md new file mode 100644 index 00000000..254870e4 --- /dev/null +++ b/docs/ordsservices/examples/resources.md @@ -0,0 +1,44 @@ +# OrdsSrvs Controller Example: External tnsnames.ora and Database credentials in an Oracle Wallet + +WIP + +TODO split files in two secrets + +### Create secrets + +Prepare a folder "resources" containig a tnsnames.ora file and an existing Oracle Wallet (created using orapki utility) containing "db.admin" and "db.adminUser.secret" credentials. + +```bash +mkdir resources + +cat >resources/tnsnames.ora <&2 +} + +sep(){ + echo "### =====================================================================================" +} + +sub(){ + echo "### $(date +'%Y-%m-%d %H:%M:%S') ### $1" +} + +#------------------------------------------------------------------------------ +function global_parameters(){ + + APEX_INSTALL=/opt/oracle/apex + # backward compatibility for ORDS images prior to 24.1.x (included) + # APEX_HOME is used only here and it is set on images <= 24.1.x + if [[ -n ${APEX_HOME} ]]; then + APEX_INSTALL=${APEX_HOME}/${APEX_VER} + echo "WARNING: APEX installation ${APEX_INSTALL}, ORDS image probably older than 24.2" + fi + APEXINS=${APEX_INSTALL}/apexins.sql + APEX_IMAGES=${APEX_INSTALL}/images + APEX_VERSION_TXT=${APEX_IMAGES}/apex_version.txt + + sub "global parameters" + echo "external_apex : ${external_apex:?}" + echo "download_apex : ${download_apex:?}" + echo "download_url_apex : ${download_url_apex:?}" + echo "APEX_INSTALL : $APEX_INSTALL" + echo "APEX_IMAGES : $APEX_IMAGES" + + # check for password encryption + if [[ -n "${ENC_PRV_KEY}" ]]; then + ENC_PRV_KEY_PATH="/opt/oracle/sa/encryptionPrivateKey" + ENC_PRV_KEY_FILE="${ENC_PRV_KEY_PATH}/${ENC_PRV_KEY}" + echo "encryption key name : ${ENC_PRV_KEY}" + [[ -f "${ENC_PRV_KEY_FILE}" ]] || echo "ERROR: encryption key not found" + javac /opt/oracle/sa/scripts/RSADecryptOAEP.java -d /opt/oracle/ords/scripts/ + fi +} + +#------------------------------------------------------------------------------ +prepare_pool_connect_string() { + local -n _conn_string="${1}" + + sub "Prepare connect string" + + local -r _admin_user=$($ords_cfg_cmd get --secret db.adminUser | tail -1) + local _conn_type + _conn_type=$($ords_cfg_cmd get db.connectionType |tail -1) + if [[ $_conn_type == "customurl" ]]; then + local -r _conn=$($ords_cfg_cmd get db.customURL | tail -1) + elif [[ $_conn_type == "tns" ]]; then + local -r _tns_service=$($ords_cfg_cmd get db.tnsAliasName | tail -1) + local -r _conn=${_tns_service} + elif [[ $_conn_type == "basic" ]]; then + local -r _host=$($ords_cfg_cmd get db.hostname | tail -1) + local -r _port=$($ords_cfg_cmd get db.port | tail -1) + local -r _service=$($ords_cfg_cmd get db.servicename | tail -1) + local -r _sid=$($ords_cfg_cmd get db.sid | tail -1) + + if [[ -n ${_host} ]] && [[ -n ${_port} ]]; then + if [[ -n ${_service} ]] || [[ -n ${_sid} ]]; then + local -r _conn=${_host}:${_port}/${_service:-$_sid} + fi + fi + else + # db wallet zip, zip file in network/admin, DbWalletSecret + _conn_type="dbWalletZip" + local -r _wallet_service=$($ords_cfg_cmd get db.wallet.zip.service | tail -1) + local -r _conn=${_wallet_service} + fi + + if [[ -n ${_conn} ]]; then + echo "Connection String (${_conn_type}): ${_conn}" + _conn_string="${_admin_user%%/ *}/${config["dbadminuserpassword"]}@${_conn}" + if [[ ${_admin_user%%/ *} == "SYS" ]]; then + _conn_string="${_conn_string=} AS SYSDBA" + fi + fi +} + +#------------------------------------------------------------------------------ +function setup_sql_environment(){ + + sub "Configuring sql environment" + + ## Get TNS_ADMIN location + local -r _tns_admin=$($ords_cfg_cmd get db.tnsDirectory 2>&1| tail -1) + if [[ ! $_tns_admin =~ "Cannot get setting" ]]; then + echo "Setting: TNS_ADMIN=${_tns_admin}" + export TNS_ADMIN=${_tns_admin} + [[ -d ${TNS_ADMIN} ]] && ls -l ${TNS_ADMIN} + fi + + ## Get ADB Wallet + #echo "Checking db.wallet.zip.path" + #echo "$ords_cfg_cmd get db.wallet.zip.path" + local -r _wallet_zip_path=$($ords_cfg_cmd get db.wallet.zip.path 2>&1| tail -1) + #echo "wallet_zip_path : \"${_wallet_zip_path}\"" + if [[ ! $_wallet_zip_path =~ "Cannot get setting" ]]; then + echo "Using: set cloudconfig ${_wallet_zip_path}" + local -r _cloudconfig="set cloudconfig ${_wallet_zip_path}" + fi + + return 0 + +} + +function run_sql { + local -r _conn_string="${1}" + local -r _sql="${2}" + local -n _output="${3}" + local -i _rc=0 + + if [[ -z ${_sql} ]]; then + dump_stack + echo "FATAL: missing SQL calling run_sql" && exit 1 + fi + + echo "Running SQL" + + # NOTE to maintainer; the heredoc must be TAB indented + _output=$(sql -S -nohistory -noupdates /nolog <<-EOSQL + WHENEVER SQLERROR EXIT 1 + WHENEVER OSERROR EXIT 1 + ${_cloudconfig} + connect $_conn_string + set serveroutput on echo off pause off feedback off + set heading off wrap off linesize 1000 pagesize 0 + SET TERMOUT OFF VERIFY OFF + ${_sql} + exit; + EOSQL + ) + _rc=$? + + if (( _rc > 0 )); then + dump_stack + echo "SQLERROR: ${_output}" + fi + + return $_rc +} + +#------------------------------------------------------------------------------ +function check_adb() { + local -r _conn_string=$1 + local -n _is_adb=$2 + + sub "ADB check" + + local -r _adb_chk_sql=" + DECLARE + invalid_column exception; + pragma exception_init (invalid_column,-00904); + adb_check integer; + BEGIN + EXECUTE IMMEDIATE q'[SELECT COUNT(*) FROM ( + SELECT JSON_VALUE(cloud_identity, '\$.DATABASE_OCID') AS database_ocid + FROM v\$pdbs) t + WHERE t.database_ocid like '%AUTONOMOUS%']' INTO adb_check; + DBMS_OUTPUT.PUT_LINE(adb_check); + EXCEPTION WHEN invalid_column THEN + DBMS_OUTPUT.PUT_LINE('0'); + END; + /" + echo "Checking if Database is an ADB" + run_sql "${_conn_string}" "${_adb_chk_sql}" "_adb_check" + _rc=$? + + if (( _rc == 0 )); then + _adb_check=${_adb_check//[[:space:]]/} + if (( _adb_check == 1 )); then + _is_adb=${_adb_check//[[:space:]]/} + echo "ADB : yes" + else + echo "ADB : no" + fi + else + echo "ADB check failed" + fi + + return ${_rc} +} + +function create_adb_user() { + local -r _conn_string="${1}" + local -r _pool_name="${2}" + + local _config_user + _config_user=$($ords_cfg_cmd get db.username | tail -1) + + if [[ -z ${_config_user} ]] || [[ ${_config_user} == "ORDS_PUBLIC_USER" ]]; then + echo "FATAL: You must specify a db.username <> ORDS_PUBLIC_USER in pool \"${_pool_name}\"" + dump_stack + return 1 + fi + + local -r _adb_user_sql=" + DECLARE + l_user VARCHAR2(255); + l_cdn VARCHAR2(255); + BEGIN + BEGIN + SELECT USERNAME INTO l_user FROM DBA_USERS WHERE USERNAME='${_config_user}'; + EXECUTE IMMEDIATE 'ALTER USER \"${_config_user}\" PROFILE ORA_APP_PROFILE'; + EXECUTE IMMEDIATE 'ALTER USER \"${_config_user}\" IDENTIFIED BY \"${config["dbpassword"]}\"'; + DBMS_OUTPUT.PUT_LINE('${_config_user} Exists - Password reset'); + EXCEPTION + WHEN NO_DATA_FOUND THEN + EXECUTE IMMEDIATE 'CREATE USER \"${_config_user}\" IDENTIFIED BY \"${config["dbpassword"]}\" PROFILE ORA_APP_PROFILE'; + DBMS_OUTPUT.PUT_LINE('${_config_user} Created'); + END; + EXECUTE IMMEDIATE 'GRANT CONNECT TO \"${_config_user}\"'; + BEGIN + SELECT USERNAME INTO l_user FROM DBA_USERS WHERE USERNAME='ORDS_PLSQL_GATEWAY_OPER'; + EXECUTE IMMEDIATE 'ALTER USER \"ORDS_PLSQL_GATEWAY_OPER\" PROFILE DEFAULT'; + EXECUTE IMMEDIATE 'ALTER USER \"ORDS_PLSQL_GATEWAY_OPER\" NO AUTHENTICATION'; + DBMS_OUTPUT.PUT_LINE('ORDS_PLSQL_GATEWAY_OPER Exists'); + EXCEPTION + WHEN NO_DATA_FOUND THEN + EXECUTE IMMEDIATE 'CREATE USER \"ORDS_PLSQL_GATEWAY_OPER\" NO AUTHENTICATION PROFILE DEFAULT'; + DBMS_OUTPUT.PUT_LINE('ORDS_PLSQL_GATEWAY_OPER Created'); + END; + EXECUTE IMMEDIATE 'GRANT CONNECT TO \"ORDS_PLSQL_GATEWAY_OPER\"'; + EXECUTE IMMEDIATE 'ALTER USER \"ORDS_PLSQL_GATEWAY_OPER\" GRANT CONNECT THROUGH \"${_config_user}\"'; + ORDS_ADMIN.PROVISION_RUNTIME_ROLE ( + p_user => '${_config_user}' + ,p_proxy_enabled_schemas => TRUE + ); + ORDS_ADMIN.CONFIG_PLSQL_GATEWAY ( + p_runtime_user => '${_config_user}' + ,p_plsql_gateway_user => 'ORDS_PLSQL_GATEWAY_OPER' + ); + -- TODO: Only do this if ADB APEX Version <> this ORDS Version + BEGIN + SELECT images_version INTO L_CDN + FROM APEX_PATCHES + where is_bundle_patch = 'Yes' + order by patch_version desc + fetch first 1 rows only; + EXCEPTION WHEN NO_DATA_FOUND THEN + select version_no INTO L_CDN + from APEX_RELEASE; + END; + apex_instance_admin.set_parameter( + p_parameter => 'IMAGE_PREFIX', + p_value => 'https://static.oracle.com/cdn/apex/'||L_CDN||'/' + ); + END; + /" + + local _adb_user_sql_output + run_sql "${_conn_string}" "${_adb_user_sql}" "_adb_user_sql_output" + _rc=$? + + echo "Installation Output: ${_adb_user_sql_output}" + return ${_rc} +} + +#------------------------------------------------------------------------------ +function apex_compare_versions() { + local _db_ver=$1 + local _im_ver=$2 + + echo "database APEX version : $_db_ver" + echo "install APEX version : $_im_ver" + + IFS='.' read -r -a _db_ver_array <<< "$_db_ver" + IFS='.' read -r -a _im_ver_array <<< "$_im_ver" + + # Compare each component + local i + for i in "${!_db_ver_array[@]}"; do + if [[ "${_db_ver_array[$i]}" -lt "${_im_ver_array[$i]}" ]]; then + # _db_ver < _im_ver (upgrade) + return 0 + elif [[ "${_db_ver_array[$i]}" -gt "${_im_ver_array[$i]}" ]]; then + # _db_ver < _im_ver (do nothing) + return 1 + fi + done + # _db_ver == __im_ver (do nothing) + return 1 +} + +#------------------------------------------------------------------------------ +ords_client_version(){ + sub "ORDS client version" + ORDSVERSION=$(ords --config "${ORDS_CONFIG}" --version 2>&1 | tail -1) + echo "ords_client_version : \"$ORDSVERSION\"" +} + +#------------------------------------------------------------------------------ +set_ords_secret() { + local -r _pool_name="${1}" + local -r _config_key="${2}" + local -r _config_val="${3}" + local -i _rc=0 + + if [[ -n "${_config_val}" ]]; then + echo "Setting ${_config_key} in pool \"${_pool_name}\"" + ords --config "$ORDS_CONFIG" config --db-pool "${_pool_name}" secret --password-stdin "${_config_key}" 2>&1 <<< "${_config_val}"|tail -1 + _rc=$? + else + echo "${_config_key} in pool \"${_pool_name}\" is not defined" + _rc=0 + fi + + return ${_rc} +} + +#------------------------------------------------------------------------------ +read_passwords(){ + + sub "Reading passwords" + for key in dbpassword dbadminuserpassword dbcdbadminuserpassword; do + var_key="${pool_name_underscore}_${key}" + echo "Obtaining value from initContainer variable: ${var_key}" + var_val="${!var_key}" + if [[ (-n "${var_val}") && (-n "${ENC_PRV_KEY}") && (-f "${ENC_PRV_KEY_FILE}") ]]; then + echo "Decrypting ${var_key}" + cd /opt/oracle/ords/scripts || return + var_val_enc=${var_val} + var_val=$(java RSADecryptOAEP "${ENC_PRV_KEY}" "${var_val_enc}") + fi + config[${key}]="${var_val}" + done + + # Set ORDS Secrets + set_ords_secret "${pool_name}" "db.password" "${config["dbpassword"]}" + rc=$((rc + $?)) + set_ords_secret "${pool_name}" "db.adminUser.password" "${config["dbadminuserpassword"]}" + rc=$((rc + $?)) + set_ords_secret "${pool_name}" "db.cdb.adminUser.password" "${config["dbcdbadminuserpassword"]}" + rc=$((rc + $?)) + + if (( rc > 0 )); then + echo "FATAL: Unable to set configuration for pool \"${pool_name}\"" + return 1 + fi + + return 0 +} + +#------------------------------------------------------------------------------ +ords_upgrade() { + local -r _pool_name="${1}" + local -r _upgrade_key="${2}" + local -i _rc=0 + + sub "ORDS install/upgrade" + + if [[ -n "${config["dbadminuserpassword"]}" ]]; then + # Get usernames + local -r ords_user=$($ords_cfg_cmd get db.username | tail -1) + local -r ords_admin=$($ords_cfg_cmd get db.adminUser | tail -1) + + echo "Performing ORDS install/upgrade as $ords_admin into $ords_user on pool \"${_pool_name}\"" + if [[ ${_pool_name} == "default" ]]; then + ords --config "$ORDS_CONFIG" install --db-only \ + --admin-user "$ords_admin" --password-stdin <<< "${config["dbadminuserpassword"]}" + _rc=$? + else + ords --config "$ORDS_CONFIG" install --db-pool "${_pool_name}" --db-only \ + --admin-user "$ords_admin" --password-stdin <<< "${config["dbadminuserpassword"]}" + _rc=$? + fi + + # Dar be bugs below deck with --db-user so using the above + # ords --config "$ORDS_CONFIG" install --db-pool "$1" --db-only \ + # --admin-user "$ords_admin" --db-user "$ords_user" --password-stdin <<< "${!2}" + fi + + return $_rc +} + + + +#------------------------------------------------------------------------------ +function get_apex_version() { + local -r _conn_string="${1}" + local -n _db_apex_version="${2}" + local -i _rc=0 + + sub "APEX version check" + + local -r _ver_sql="SELECT VERSION FROM DBA_REGISTRY WHERE COMP_ID='APEX';" + #local -r _ver_sql="SELECT SCHEMA FROM DBA_REGISTRY WHERE COMP_ID='APEX';" + run_sql "${_conn_string}" "${_ver_sql}" "_db_apex_version" + _rc=$? + + if (( _rc > 0 )); then + echo "FATAL: Unable to get APEX version" + dump_stack + return $_rc + fi + + _db_apex_version=${_db_apex_version//[^0-9.]/} + #_db_apex_version="${_db_apex_version//[[:space:]]}" + if [[ -z "${_db_apex_version}" ]]; then + _db_apex_version="NotInstalled" + fi + echo "Database APEX Version: ${_db_apex_version}" + + return $_rc +} + +#------------------------------------------------------------------------------ +function get_apex_action(){ + local -r _conn_string="${1}" + local -r _db_apex_version="${2}" + local -n _action="${3}" + local -i _rc=0 + + sub "APEX installation check" + + _action="error" + if [[ ( -z "${_db_apex_version}" ) || ( "${_db_apex_version}" == "NotInstalled" ) ]]; then + echo "Installing APEX ${APEX_VER}" + _action="install" + elif apex_compare_versions "${_db_apex_version}" "${APEX_VER}"; then + echo "Upgrading from ${_db_apex_version} to ${APEX_VER}" + _action="upgrade" + else + echo "No Installation/Upgrade Required" + _action="none" + fi + + return $_rc +} + +#------------------------------------------------------------------------------ +function check_apex_installation_version(){ + + sub "APEX installation files" + + if [[ ! (-f ${APEX_VERSION_TXT}) ]]; then + echo "APEX installation not found, missing ${APEX_VERSION_TXT}" + return 1 + fi + + APEX_VER=$(cat "${APEX_VERSION_TXT}"|grep Version|cut -f2 -d:|tr -d '[:space:]') + echo "APEX_VER: ${APEX_VER}" +} + +#------------------------------------------------------------------------------ +function apex_external(){ + sub "APEX external" + + if [[ ${external_apex} != "true" ]]; then + echo "APEX external disabled" + return 0 + fi + + id + df -h "$APEX_INSTALL" + ls -ld "$APEX_INSTALL" + ls -l "$APEX_INSTALL" + ls -l "${APEX_VERSION_TXT}" + echo test >> "$APEX_INSTALL/test.txt" + ls -l "$APEX_INSTALL/test.txt" + + while true + do + date +"%Y-%m-%d %H:%M:%S" + if [[ -f ${APEX_VERSION_TXT} ]] + then + echo Found images/apex_version.txt + break + else + if [[ -f ${APEX_INSTALL}/apex.zip ]] + then + date + echo "Found ${APEX_INSTALL}/apex.zip, extracting ..." + cd "${APEX_INSTALL}" || return + jar xf apex.zip + mv "${APEX_INSTALL}/apex/"* "${APEX_INSTALL}" + date + echo completed + else + date + echo "Missing ${APEX_INSTALL}/apex.zip, manually copy apex.zip in ${APEX_INSTALL} on the init container of the pod" + echo "e.g. kubectl cp /tmp/apex.zip ordssrvs-697c5698d9-q8gxf:/opt/oracle/apex/ -n testcase -c ordssrvs-init" + sleep 5 + fi + fi + done + +} + +#------------------------------------------------------------------------------ +function apex_download(){ + + sub "APEX download" + + if [[ ${download_apex} != "true" ]]; then + echo "APEX download disabled" + return 0 + fi + + mkdir -p "${APEX_INSTALL}" + rm -rf "${APEX_INSTALL:?}/*" + cd /tmp || return + echo "Downloading ${download_url_apex}" + curl -o apex.zip "${download_url_apex}" + echo "Extracting apex.zip" + jar xf apex.zip + mv "/tmp/apex/*" "${APEX_INSTALL}" + + if [[ ! (-f ${APEXINS}) ]]; then + echo "ERROR: ${APEXINS} not found, APEX download failed" + return 1 + fi + echo "APEX_INSTALL: ${APEX_INSTALL}" + + # config can be read-only, it will be set again at command-line ords start + sub "Configuring ORDS images" + echo "APEX_IMAGES: ${APEX_IMAGES}" + ords config set standalone.static.path "${APEX_IMAGES}" + + return 0 +} + +#------------------------------------------------------------------------------ +function apex_upgrade() { + local -r _conn_string="${1}" + local -r _upgrade_key="${2}" + local -i _rc=0 + + sub "APEX Installation/Upgrade" + + if [[ -z ${APEX_INSTALL} ]]; then + echo "ERROR: APEX_INSTALL not set" + return 1 + fi + + if [[ ! ( -f ${APEX_INSTALL}/apexins.sql ) ]]; then + echo "ERROR: ${APEX_INSTALL}/apexins.sql not found" + return 1 + fi + + + if [[ "${!_upgrade_key}" = "true" ]]; then + echo "Starting Installation of APEX" + cd "${APEX_INSTALL}" || return 1 + SEC=${config["dbpassword"]} + local -r _install_sql="@apxsilentins.sql SYSAUX SYSAUX TEMP /i/ $SEC $SEC $SEC $SEC" + run_sql "${_conn_string}" "${_install_sql}" "_install_output" + _rc=$? + echo "Installation Output: ${_install_output:?}" + fi + + return $_rc +} + +#------------------------------------------------------------------------------ +function apex_housekeeping(){ + + # check database APEX version regardless of APEX parameters + get_apex_version "${conn_string}" "db_apex_version" + if [[ -z ${db_apex_version} ]]; then + echo "FATAL: Unable to get APEX Version for pool \"${pool_name}\"" + return 1 + fi + + # check if apex upgrade is enabled + if [[ ${apex_upgrade} != "true" ]]; then + echo "APEX Install/Upgrade not requested for pool \"${pool_name}\"" + return 0 + fi + + # get suggested action + get_apex_action "${conn_string}" "${db_apex_version}" "db_apex_action" + if [[ -z ${db_apex_action} ]]; then + echo "FATAL: Unable to get APEX suggested action for pool \"${pool_name}\"" + return 1 + fi + + # upgrade + echo "APEX version : \"${db_apex_version}\"" + echo "APEX suggested action : $db_apex_action" + if [[ ${db_apex_action} != "none" ]]; then + apex_upgrade "${conn_string}" "${pool_name_underscore}_autoupgrade_apex" + rc=$? + if (( rc > 0 )); then + echo "FATAL: Unable to ${db_apex_action} APEX for pool \"${pool_name}\"" + return 1 + fi + fi + + +} + + + +#------------------------------------------------------------------------------ +function pool_parameters(){ + + sub "pool parameters" + apex_upgrade_var=${pool_name_underscore}_autoupgrade_apex + apex_upgrade=${!apex_upgrade_var} + [[ -z $apex_upgrade ]] && apex_upgrade=false + echo "${pool_name} - autoupgrade_apex : ${apex_upgrade}" + + ords_upgrade_var=${pool_name_underscore}_autoupgrade_ords + ords_upgrade=${!ords_upgrade_var} + [[ -z $ords_upgrade ]] && ords_upgrade=false + echo "${pool_name} - autoupgrade_ords : ${ords_upgrade}" + +} + + +#------------------------------------------------------------------------------ +# INIT +#------------------------------------------------------------------------------ +declare -A pool_exit +sep +sub "ORDSSRVS init" +# FIXME remove +env|sort + +sep +global_parameters +ords_client_version +apex_download +apex_external + +# check APEX installation files version, downloaded or mounted by PVC +check_apex_installation_version + +for pool in "$ORDS_CONFIG"/databases/*; do + rc=0 + pool_name=$(basename "$pool") + pool_name_underscore=${pool_name//-/_} + pool_exit[${pool_name}]=0 + ords_cfg_cmd="ords --config $ORDS_CONFIG config --db-pool ${pool_name}" + declare -A config + + sep + sub "Pool ${pool_name}" + echo "poolName: ${pool_name}" + + pool_parameters + + read_passwords + rc=$? + if (( rc > 0 )); then + pool_exit[${pool_name}]=1 + continue + fi + + sub "SQL setup" + pool_ords_wallet_path="${ORDS_CONFIG}/databases/${pool_name}/wallet" + pool_ords_wallet="${pool_ords_wallet_path}/cwallet.sso" + if [[ (-z ${config["dbadminuserpassword"]}) && (-f "${pool_ords_wallet}") ]] + then + ls -l "${pool_ords_wallet}" + + # FIXME evaluate to skip sql check or extract admin password from wallet + + #echo "Admin password not set and wallet already exists, ignoring sql setup" + #pool_exit[${pool_name}]=0 + #continue + + echo "Admin password not set and wallet exists, reading admin password from wallet" + PKILIB="/opt/oracle/sqlcl/lib/oraclepki.jar" + PKICLASS="oracle.security.pki.OracleSecretStoreTextUI" + PKIENTRY="db.adminUser.password" + config["dbadminuserpassword"]=$(java -cp ${PKILIB} ${PKICLASS} -wrl ${pool_ords_wallet_path} -viewEntry ${PKIENTRY}|grep ${PKIENTRY}|cut -f2 -d\=|cut -f2 -d\ ) + fi + + if [[ (-z ${config["dbadminuserpassword"]}) ]] + then + echo "ERROR: admin password not set, aborting sql setup" + pool_exit[${pool_name}]=1 + continue + fi + + prepare_pool_connect_string "conn_string" + if [[ -z ${conn_string} ]]; then + echo "Unable to get database connect string for pool \"${pool_name}\"" + dump_stack + pool_exit[${pool_name}]=1 + continue + fi + + setup_sql_environment + rc=$? + if (( rc > 0 )); then + pool_exit[${pool_name}]=1 + continue + fi + + sub "SQL Test" + if [[ (-n ${conn_string}) || (-n ${_cloudconfig}) ]] + then + sql /nolog 2>&1 <<-SQL + set lines 1000 pages 100 feed off + WHENEVER SQLERROR EXIT SQL.ERROR + WHENEVER OSERROR EXIT 1 + ${_cloudconfig} + connect ${conn_string} + alter session set nls_date_format='dd/mm/yyyy hh24:mi:ss'; + SELECT sysdate, host_name, user, instance_name, SYS_CONTEXT('USERENV', 'CON_NAME') AS current_pdb FROM V\$instance; + SQL + rc=$? + echo + if (( rc > 0 )); then + echo "ERROR: SQL Test FAILED, sql exit code ${rc}" + pool_exit[${pool_name}]=1 + continue + fi + else + echo "" + fi + + is_adb=false + check_adb "${conn_string}" "is_adb" + rc=$? + if (( rc > 0 )); then + pool_exit[${pool_name}]=1 + continue + fi + + if (( is_adb )); then + # Create ORDS User + echo "Processing ADB in Pool: ${pool_name}" + create_adb_user "${conn_string}" "${pool_name}" + continue + fi + + # not ADB + + # APEX + apex_housekeeping + rc=$? + if (( rc > 0 )); then + echo "FATAL: unable to manage APEX configuration for pool \"${pool_name}\"" + dump_stack + pool_exit[${pool_name}]=1 + continue + fi + + # database ORDS + if [[ ${ords_upgrade} == "true" ]]; then + ords_upgrade "${pool_name}" "${pool_name_underscore}_autoupgrade_ords" + rc=$? + if (( rc > 0 )); then + echo "FATAL: Unable to perform requested ORDS install/upgrade on pool \"${pool_name}\"" + pool_exit[${pool_name}]=1 + dump_stack + fi + else + echo "ORDS Install/Upgrade not requested for pool \"${pool_name}\"" + fi +done + +sep +sub "Exit codes" +rc=1 +for key in "${!pool_exit[@]}"; do + printf "Pool: %-16s Exit Code: %s\n" "${key}" "${pool_exit[${key}]}" + if (( ${pool_exit[$key]} == 0 )); then + rc=0 + fi +done + +exit $rc diff --git a/ordssrvs/ords_start.sh b/ordssrvs/ords_start.sh new file mode 100644 index 00000000..7959088f --- /dev/null +++ b/ordssrvs/ords_start.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +date +"%Y-%m-%d %H:%M:%S" +echo "=== ORDS start ===" +echo "ORDS_CONFIG: ${ORDS_CONFIG}" + +unset APEX_IMAGES + +# old path, until ORDS image 24.1.1 +if [[ ! (-z ${APEX_BASE}) && ! (-z ${APEX_VER}) && (-d ${APEX_BASE}/${APEX_VER}/images) ]]; then + APEX_IMAGES=${APEX_BASE}/${APEX_VER}/images +fi + +# downloaded image path +if [[ -d /opt/oracle/apex/images ]]; then + APEX_IMAGES=/opt/oracle/apex/images +fi + +if [[ -z ${APEX_IMAGES} ]]; then + echo "APEX_IMAGES not found" + ords --config "${ORDS_CONFIG}" serve +else + echo "APEX_IMAGES: ${APEX_IMAGES}" + ords --config "${ORDS_CONFIG}" serve --apex-images "${APEX_IMAGES}" +fi + From 42804b18f8a73dcdd5f789a332816911826f8286 Mon Sep 17 00:00:00 2001 From: marcstef Date: Wed, 5 Nov 2025 18:02:29 +0000 Subject: [PATCH 2/4] tnsnames.ora and pool wallet documentation --- README.md | 4 +- docs/ordsservices/examples/resources.md | 68 ++++++++++++++++--------- 2 files changed, 45 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 0de4b13f..5e436afd 100644 --- a/README.md +++ b/README.md @@ -8,10 +8,10 @@ >* **Secret Env Injection** >Passwords are injected via Kubernetes Secrets into the init container. Secret values are not shown in kubectl describe pod output; only references (e.g., Secret names/keys) may appear. >* **TNSAdminSecret** ->adding a dedicated documentation page. see [resources](./docs/ordsservices/examples/resources.md") +>adding a dedicated documentation page. see [resources](./docs/ordsservices/examples/resources.md) >* **poolWalletSecret** >New attribute poolWalletSecret lets you reference an external Oracle Wallet that contains the database credentials, avoiding storage of the password in a Kubernetes Secret. -see [resources](./docs/ordsservices/examples/resources.md") +see [resources](./docs/ordsservices/examples/resources.md) > >**HOWTO** >To build your own Oracle Operator image use this: diff --git a/docs/ordsservices/examples/resources.md b/docs/ordsservices/examples/resources.md index 254870e4..47492ee2 100644 --- a/docs/ordsservices/examples/resources.md +++ b/docs/ordsservices/examples/resources.md @@ -1,44 +1,62 @@ -# OrdsSrvs Controller Example: External tnsnames.ora and Database credentials in an Oracle Wallet +# OrdsSrvs Example: external tnsnames.ora and Oracle Wallet -WIP +This example demonstrates how to: +- Provide an external tnsnames.ora to the OrdsSrvs controller via a Kubernetes Secret. +- Supply database credentials using an Oracle Wallet stored in a Kubernetes Secret. -TODO split files in two secrets +>Note: The attributes tnsAdminSecret and poolWalletSecret are independent and can be used separately. -### Create secrets - -Prepare a folder "resources" containig a tnsnames.ora file and an existing Oracle Wallet (created using orapki utility) containing "db.admin" and "db.adminUser.secret" credentials. +## Create a tnsnames.ora Secret +Prepare a folder named "resources" containing a tnsnames.ora file, then create a Kubernetes Secret from it. ```bash -mkdir resources - -cat >resources/tnsnames.ora < resources/tnsnames.ora <)(PORT = )) (CONNECT_DATA = - (SERVICE_NAME = $PDBNAME ) + (SERVICE_NAME = ) ) ) EOF -cp saved/cwallet.sso resources/cwallet.sso +kubectl create secret -n generic myresources-tns-admin --from-file=./resources/tnsnames.ora +``` + +Replace \, \, \, and \ with your values. + +## Prepare Credentials in an Oracle Wallet -kubectl create secret -n testcase generic myresources --from-file=./resources +Use an existing Oracle Wallet (created with the orapki utility) that contains the credentials (entries for db.admin and db.adminUser.secret), or create a new wallet with orapki. In this example, we reuse a wallet from a previous OrdsSrvs installation. +```bash +mkdir -p resources +cp ./saved/cwallet.sso ./resources/cwallet.sso + +kubectl create secret -n generic myresources-pool-wallet --from-file=./resources/cwallet.sso ``` -### Create ordssrvs Resource +## Create the OrdsSrvs Resource -Example yaml file using external tnsnames.ora and Oracle wallet. +Below is a snippet (not a complete manifest) showing how to reference the external tnsnames.ora and the Oracle Wallet in a pool of the OrdsSrvs resource. ```yaml - - poolName: wallet-pool - db.connectionType: tns - db.tnsAliasName: TESTCASE - tnsAdminSecret: - secretName: myresources - poolWalletSecret: - secretName: myresources - db.username: ORDS_PUBLIC_USER - db.adminUser: SYS -``` \ No newline at end of file +... +- poolName: example-pool + db.connectionType: tns + db.tnsAliasName: TESTCASE + tnsAdminSecret: + secretName: myresources-tns-admin + poolWalletSecret: + secretName: myresources-pool-wallet + db.username: ORDS_PUBLIC_USER + db.adminUser: SYS +... +``` + +## Notes + +- tnsAdminSecret and poolWalletSecret have no dependencies on each other and can be used independently. +- Ensure the db.tnsAliasName matches the alias present in tnsnames.ora (TESTCASE in this example). +- Handle Kubernetes Secrets and wallet files in accordance with your organization’s security and compliance policies. \ No newline at end of file From 96518e108834d63d1fd5d200e553ed5d9a76a44f Mon Sep 17 00:00:00 2001 From: marcstef Date: Mon, 24 Nov 2025 10:12:31 +0000 Subject: [PATCH 3/4] central.config.url - Central Configuration Manager --- README.md | 12 +- apis/database/v4/ordssrvs_types.go | 27 +- controllers/database/ordssrvs_controller.go | 172 ++++++--- controllers/database/ordssrvs_ordsconfig.go | 4 +- docs/ordsservices/README.md | 48 ++- docs/ordsservices/api.md | 12 +- .../examples/central_configuration.md | 340 ++++++++++++++++ ordssrvs/ords_init.sh | 365 +++++++++++------- ordssrvs/ords_start.sh | 10 + 9 files changed, 770 insertions(+), 220 deletions(-) create mode 100644 docs/ordsservices/examples/central_configuration.md diff --git a/README.md b/README.md index 5e436afd..4b92b747 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,21 @@ ># ⚠️ **OrdsSrvs Controller Development Branch** ⚠️ -> +> >This is a development branch for **OrdsSrvs Controller new features**. It is not the main/stable branch and should not be used for production. > +> [OrdsSrvs Controller](./docs/ordsservices/README.md) +> >**CHANGELOG** ->* **Standard Kubernetes Secret** +>* **Native Kubernetes Secret** >**encPrivKey** attribute is now optional; passwords can be stored in native K8s Secrets without additional encryption. >* **Secret Env Injection** >Passwords are injected via Kubernetes Secrets into the init container. Secret values are not shown in kubectl describe pod output; only references (e.g., Secret names/keys) may appear. ->* **TNSAdminSecret** +>* **TNSAdminSecret** >adding a dedicated documentation page. see [resources](./docs/ordsservices/examples/resources.md) >* **poolWalletSecret** >New attribute poolWalletSecret lets you reference an external Oracle Wallet that contains the database credentials, avoiding storage of the password in a Kubernetes Secret. see [resources](./docs/ordsservices/examples/resources.md) +>* **central.config.url** +Deploying ORDS with Central Configuration Server (e.g. https://central-config.example.com:8585/central/v1/config) > >**HOWTO** >To build your own Oracle Operator image use this: @@ -36,7 +40,7 @@ As part of Oracle's resolution to make Oracle Database Kubernetes native (that i * Provision, add & delete asm disks, and more * **ORDS Service** * ServiceAccount and OpenShift support - * Auto download of APEX installation files and APEX image on a Persistent Volume + * Auto download of APEX installation files and APEX images on a Persistent Volume * **Integrations** * Private Cloud Appliance (PCA) * Compute Cloud@Customer (C3) diff --git a/apis/database/v4/ordssrvs_types.go b/apis/database/v4/ordssrvs_types.go index 61ffb210..22f6b7a5 100644 --- a/apis/database/v4/ordssrvs_types.go +++ b/apis/database/v4/ordssrvs_types.go @@ -46,34 +46,52 @@ import ( // OrdsSrvsSpec defines the desired state of OrdsSrvs // +kubebuilder:resource:shortName="ords" type OrdsSrvsSpec struct { + // Specifies the desired Kubernetes Workload //+kubebuilder:validation:Enum=Deployment;StatefulSet;DaemonSet //+kubebuilder:default=Deployment WorkloadType string `json:"workloadType,omitempty"` + // Defines the number of desired Replicas when workloadType is Deployment or StatefulSet //+kubebuilder:validation:Minimum=1 //+kubebuilder:default=1 Replicas int32 `json:"replicas,omitempty"` + // Specifies whether to restart pods when Global or Pool configurations change ForceRestart bool `json:"forceRestart,omitempty"` + // Specifies the ORDS container image //+kubecbuilder:default=container-registry.oracle.com/database/ords:latest Image string `json:"image"` + // Specifies the ORDS container image pull policy //+kubebuilder:validation:Enum=IfNotPresent;Always;Never //+kubebuilder:default=IfNotPresent ImagePullPolicy corev1.PullPolicy `json:"imagePullPolicy,omitempty"` + // Specifies the Secret Name for pulling the ORDS container image ImagePullSecrets string `json:"imagePullSecrets,omitempty"` + // Contains settings that are configured across the entire ORDS instance. - GlobalSettings GlobalSettings `json:"globalSettings"` - // Contains settings for individual pools/databases + //+kubebuilder:default:={} + GlobalSettings GlobalSettings `json:"globalSettings,omitempty"` + // Private key EncPrivKey PasswordSecret `json:"encPrivKey,omitempty"` + + // Contains settings for individual pools/databases PoolSettings []*PoolSettings `json:"poolSettings,omitempty"` - // +k8s:openapi-gen=true + // ServiceAccount of the OrdsSrvs Pod + // +k8s:openapi-gen=true ServiceAccountName string `json:"serviceAccountName,omitempty"` + + // Central Configuration URL + CentralConfigUrl string `json:"central.config.url,omitempty"` + + // Central Configuration Wallet + CentralConfigWallet string `json:"central.config.wallet,omitempty"` + } type GlobalSettings struct { @@ -689,6 +707,7 @@ type OrdsSrvsStatus struct { // +operator-sdk:csv:customresourcedefinitions:type=status Conditions []metav1.Condition `json:"conditions,omitempty" patchStrategy:"merge" patchMergeKey:"type" protobuf:"bytes,1,rep,name=conditions"` + } //+kubebuilder:object:root=true @@ -711,6 +730,7 @@ type OrdsSrvs struct { Spec OrdsSrvsSpec `json:"spec,omitempty"` Status OrdsSrvsStatus `json:"status,omitempty"` + } //+kubebuilder:object:root=true @@ -722,6 +742,7 @@ type OrdsSrvsList struct { Items []OrdsSrvs `json:"items"` } + func init() { SchemeBuilder.Register(&OrdsSrvs{}, &OrdsSrvsList{}) } diff --git a/controllers/database/ordssrvs_controller.go b/controllers/database/ordssrvs_controller.go index a1e3646f..6cdd3295 100644 --- a/controllers/database/ordssrvs_controller.go +++ b/controllers/database/ordssrvs_controller.go @@ -97,15 +97,7 @@ const ( typeUnsyncedORDS = "Unsynced" ) -// Definitions used in the controller -var ordssrvsScriptsConfigMapName = "" -var ordssrvsGlobalSettingsConfigMapName = "" -var RSADecryptOAEPScript string = "" -var APEXInstallationExternal string = "false" -var passwordEncryption = true -// Trigger a restart of Pods on Config Changes -var RestartPods bool = false // OrdsSrvsReconciler reconciles a OrdsSrvs object type OrdsSrvsReconciler struct { @@ -113,6 +105,14 @@ type OrdsSrvsReconciler struct { Scheme *runtime.Scheme Recorder record.EventRecorder Log logr.Logger + + ordssrvsScriptsConfigMapName string + ordssrvsGlobalSettingsConfigMapName string + APEXInstallationExternal string + passwordEncryption bool + + // Trigger a restart of Pods on Config Changes + RestartPods bool } //+kubebuilder:rbac:groups=database.oracle.com,resources=ordssrvs,verbs=get;list;watch;create;update;patch;delete @@ -151,6 +151,7 @@ func (r *OrdsSrvsReconciler) SetupWithManager(mgr ctrl.Manager) error { func (r *OrdsSrvsReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { logger := log.FromContext(ctx).WithName("Reconcile") ordssrvs := &dbapi.OrdsSrvs{} + r.RestartPods=false // Check if resource exists or was deleted if err := r.Get(ctx, req.NamespacedName, ordssrvs); err != nil { @@ -164,17 +165,32 @@ func (r *OrdsSrvsReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c // empty encryption key if ordssrvs.Spec.EncPrivKey == (dbapi.PasswordSecret{}) { - passwordEncryption = false - logger.Info("Encryption key not set (EncPrivKey)") + r.passwordEncryption = false + logger.Info("Password Encryption key (EncPrivKey) not set") + }else{ + r.passwordEncryption = true + logger.Info("Password Encryption key (EncPrivKey) from secret "+ordssrvs.Spec.EncPrivKey.SecretName+", key "+ordssrvs.Spec.EncPrivKey.PasswordKey) + } + + // Central Configuration + if ordssrvs.Spec.CentralConfigUrl != ""{ + logger.Info("Using Central Configuration from URL : "+ ordssrvs.Spec.CentralConfigUrl) + } + + // empty Global Settings + if ordssrvs.Spec.GlobalSettings == (dbapi.GlobalSettings{}) { + logger.Info("Global Settings is empty, using default values") } // APEXInstallationExternal // true: persistence volume // false: no persistence volume if ordssrvs.Spec.GlobalSettings.APEXInstallationPersistence.VolumeName != "" || ordssrvs.Spec.GlobalSettings.APEXInstallationPersistence.StorageClass != "" { - APEXInstallationExternal = "true" + r.APEXInstallationExternal = "true" + } else { + r.APEXInstallationExternal = "false" } - logger.Info("Setting env external_apex to " + APEXInstallationExternal) + logger.Info("Setting env external_apex to " + r.APEXInstallationExternal) // Set the status as Unknown when no status are available if len(ordssrvs.Status.Conditions) == 0 { @@ -184,16 +200,16 @@ func (r *OrdsSrvsReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c } } - ordssrvsScriptsConfigMapName = ordssrvs.Name + "-scripts-config-map" - ordssrvsGlobalSettingsConfigMapName = ordssrvs.Name + "-global-settings-config-map" + r.ordssrvsScriptsConfigMapName = ordssrvs.Name + "-scripts-config-map" + r.ordssrvsGlobalSettingsConfigMapName = ordssrvs.Name + "-global-settings-config-map" // ConfigMap - Scripts - if err := r.ConfigMapReconcile(ctx, ordssrvs, ordssrvsScriptsConfigMapName, 0); err != nil { + if err := r.ConfigMapReconcile(ctx, ordssrvs, r.ordssrvsScriptsConfigMapName, 0); err != nil { logger.Error(err, "Error in ConfigMapReconcile (init-script)") return ctrl.Result{}, err } - if APEXInstallationExternal == "true" { + if r.APEXInstallationExternal == "true" { // ApexInstallation PVC if err := r.ApexInstallationPVCReconcile(ctx, ordssrvs); err != nil { logger.Error(err, "Error in ApexInstallation PVC reconcile") @@ -204,7 +220,7 @@ func (r *OrdsSrvsReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c } // ConfigMap - Global Settings - if err := r.ConfigMapReconcile(ctx, ordssrvs, ordssrvsGlobalSettingsConfigMapName, 0); err != nil { + if err := r.ConfigMapReconcile(ctx, ordssrvs, r.ordssrvsGlobalSettingsConfigMapName, 0); err != nil { logger.Error(err, "Error in ConfigMapReconcile (Global)") return ctrl.Result{}, err } @@ -213,7 +229,7 @@ func (r *OrdsSrvsReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c definedPools := make(map[string]bool) for i := 0; i < len(ordssrvs.Spec.PoolSettings); i++ { poolName := strings.ToLower(ordssrvs.Spec.PoolSettings[i].PoolName) - poolConfigMapName := "config-database-pool-" + poolName + poolConfigMapName := ordssrvs.Name + "-config-database-pool-" + poolName if definedPools[poolConfigMapName] { return ctrl.Result{}, errors.New("poolName: " + poolName + " is not unique") } @@ -241,7 +257,7 @@ func (r *OrdsSrvsReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c // } // Set the Type as Unsynced when a pod restart is required - if RestartPods { + if r.RestartPods { condition := metav1.Condition{Type: typeUnsyncedORDS, Status: metav1.ConditionTrue, Reason: "Unsynced", Message: "Configurations have changed"} if err := r.SetStatus(ctx, req, ordssrvs, condition); err != nil { return ctrl.Result{}, err @@ -269,7 +285,7 @@ func (r *OrdsSrvsReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c } // Set the Type as Available when a pod restart is not required - if !RestartPods { + if !r.RestartPods { condition := metav1.Condition{Type: typeAvailableORDS, Status: metav1.ConditionTrue, Reason: "Available", Message: "Workload in Sync"} if err := r.SetStatus(ctx, req, ordssrvs, condition); err != nil { return ctrl.Result{}, err @@ -345,7 +361,7 @@ func (r *OrdsSrvsReconciler) SetStatus(ctx context.Context, req ctrl.Request, or ords.Status.HTTPPort = ords.Spec.GlobalSettings.StandaloneHTTPPort ords.Status.HTTPSPort = ords.Spec.GlobalSettings.StandaloneHTTPSPort ords.Status.MongoPort = mongoPort - ords.Status.RestartRequired = RestartPods + ords.Status.RestartRequired = r.RestartPods if err := r.Status().Update(ctx, ords); err != nil { logr.Error(err, "Failed to update Status") return err @@ -401,7 +417,7 @@ func (r *OrdsSrvsReconciler) ConfigMapReconcile(ctx context.Context, ordssrvs *d return err } logr.Info("Created: " + configMapName) - RestartPods = true + r.RestartPods = true r.Recorder.Eventf(ordssrvs, corev1.EventTypeNormal, "Create", "ConfigMap %s Created", configMapName) // Requery for comparison if err := r.Get(ctx, types.NamespacedName{Name: configMapName, Namespace: ordssrvs.Namespace}, definedConfigMap); err != nil { @@ -416,7 +432,7 @@ func (r *OrdsSrvsReconciler) ConfigMapReconcile(ctx context.Context, ordssrvs *d return err } logr.Info("Updated: " + configMapName) - RestartPods = true + r.RestartPods = true r.Recorder.Eventf(ordssrvs, corev1.EventTypeNormal, "Update", "ConfigMap %s Updated", configMapName) } return nil @@ -528,7 +544,7 @@ func (r *OrdsSrvsReconciler) WorkloadReconcile(ctx context.Context, req ctrl.Req return err } logr.Info("Created: " + kind) - RestartPods = false + r.RestartPods = false r.Recorder.Eventf(ordssrvs, corev1.EventTypeNormal, "Create", "Created %s", kind) return nil @@ -552,11 +568,11 @@ func (r *OrdsSrvsReconciler) WorkloadReconcile(ctx context.Context, req ctrl.Req if err := r.Client.Update(ctx, desiredWorkload); err != nil { return err } - RestartPods = true + r.RestartPods = true r.Recorder.Eventf(ordssrvs, corev1.EventTypeNormal, "Update", "Updated %s", kind) } - if RestartPods && ordssrvs.Spec.ForceRestart { + if r.RestartPods && ordssrvs.Spec.ForceRestart { logr.Info("Cycling: " + kind) labelsField := reflect.ValueOf(desiredWorkload).Elem().FieldByName("Spec").FieldByName("Template").FieldByName("ObjectMeta").FieldByName("Labels") if labelsField.IsValid() { @@ -567,7 +583,7 @@ func (r *OrdsSrvsReconciler) WorkloadReconcile(ctx context.Context, req ctrl.Req return err } r.Recorder.Eventf(ordssrvs, corev1.EventTypeNormal, "Restart", "Restarted %s", kind) - RestartPods = false + r.RestartPods = false } } @@ -666,7 +682,7 @@ func selectorDefine(ords *dbapi.OrdsSrvs) metav1.LabelSelector { func (r *OrdsSrvsReconciler) podTemplateSpecDefine(ords *dbapi.OrdsSrvs, ctx context.Context, _ ctrl.Request) corev1.PodTemplateSpec { labels := getLabels(ords.Name) - specVolumes, specVolumeMounts := VolumesDefine(ctx, ords) + specVolumes, specVolumeMounts := r.VolumesDefine(ctx, ords) envPorts := []corev1.ContainerPort{ { @@ -723,7 +739,7 @@ func (r *OrdsSrvsReconciler) podTemplateSpecDefine(ords *dbapi.OrdsSrvs, ctx con } // Volumes -func VolumesDefine(ctx context.Context, ordssrvs *dbapi.OrdsSrvs) ([]corev1.Volume, []corev1.VolumeMount) { +func (r *OrdsSrvsReconciler) VolumesDefine(ctx context.Context, ordssrvs *dbapi.OrdsSrvs) ([]corev1.Volume, []corev1.VolumeMount) { logger := log.FromContext(ctx).WithName("VolumesDefine") @@ -732,12 +748,12 @@ func VolumesDefine(ctx context.Context, ordssrvs *dbapi.OrdsSrvs) ([]corev1.Volu var volumeMounts []corev1.VolumeMount // scripts - scriptsVolume := volumeBuild(ordssrvsScriptsConfigMapName, ordssrvsScriptsConfigMapName, "ConfigMap", 0770) - scriptsVolumeMount := volumeMountBuild(ordssrvsScriptsConfigMapName, ordsSABase+"/scripts", true) + scriptsVolume := volumeBuild(r.ordssrvsScriptsConfigMapName, r.ordssrvsScriptsConfigMapName, "ConfigMap", 0770) + scriptsVolumeMount := volumeMountBuild(r.ordssrvsScriptsConfigMapName, ordsSABase+"/scripts", true) volumes = append(volumes, scriptsVolume) volumeMounts = append(volumeMounts, scriptsVolumeMount) - if passwordEncryption { + if r.passwordEncryption { secretName := ordssrvs.Spec.EncPrivKey.SecretName encryptionKeyVolume := volumeBuild(secretName, secretName, "Secret") encryptionKeyVolumeMount := volumeMountBuild(secretName, "/opt/oracle/sa/encryptionPrivateKey/", true) @@ -746,9 +762,9 @@ func VolumesDefine(ctx context.Context, ordssrvs *dbapi.OrdsSrvs) ([]corev1.Volu volumeMounts = append(volumeMounts, encryptionKeyVolumeMount) } - if APEXInstallationExternal == "true" { + if r.APEXInstallationExternal == "true" { // volume for APEX installation, same optional folder as for ORDS image - apexInstallationVolume := APEXInstallationVolumeDefine(ctx, ordssrvs) + apexInstallationVolume := r.APEXInstallationVolumeDefine(ctx, ordssrvs) apexInstallationReadOnly := false apexInstallationVolumeMount := volumeMountBuild(APEXInstallationPV, APEXInstallationMount, apexInstallationReadOnly) volumes = append(volumes, apexInstallationVolume) @@ -768,8 +784,8 @@ func VolumesDefine(ctx context.Context, ordssrvs *dbapi.OrdsSrvs) ([]corev1.Volu globalLogVolume := volumeBuild("sa-log-global", "", "EmptyDir") globalLogVolumeMount := volumeMountBuild("sa-log-global", ordsSABase+"/log/global/", false) - globalConfigVolume := volumeBuild(ordssrvsGlobalSettingsConfigMapName, ordssrvsGlobalSettingsConfigMapName, "ConfigMap") - globalConfigVolumeMount := volumeMountBuild(ordssrvsGlobalSettingsConfigMapName, ordsSABase+"/config/global/", true) + globalConfigVolume := volumeBuild(r.ordssrvsGlobalSettingsConfigMapName, r.ordssrvsGlobalSettingsConfigMapName, "ConfigMap") + globalConfigVolumeMount := volumeMountBuild(r.ordssrvsGlobalSettingsConfigMapName, ordsSABase+"/config/global/", true) globalDocRootVolume := volumeBuild("sa-doc-root", "", "EmptyDir") globalDocRootVolumeMount := volumeMountBuild("sa-doc-root", ordsSABase+"/config/global/doc_root/", false) @@ -793,14 +809,14 @@ func VolumesDefine(ctx context.Context, ordssrvs *dbapi.OrdsSrvs) ([]corev1.Volu poolName := strings.ToLower(ordssrvs.Spec.PoolSettings[i].PoolName) // /opt/oracle/sa/config/databases/POOL/ - poolConfigName := "config-database-pool-" + poolName + poolConfigName := ordssrvs.Name+"-config-database-pool-" + poolName poolConfigVolume := volumeBuild(poolConfigName, poolConfigName, "ConfigMap") poolConfigVolumeMount := volumeMountBuild(poolConfigName, ordsSABase+"/config/databases/"+poolName+"/", true) volumes = append(volumes, poolConfigVolume) volumeMounts = append(volumeMounts, poolConfigVolumeMount) // PoolWalletSecret -> /opt/oracle/sa/config/databases/POOL/wallet/ - poolWalletVolumeName := "config-database-pool-wallet-" + poolName + poolWalletVolumeName := ordssrvs.Name+"-config-database-pool-wallet-" + poolName poolWalletVolumePath := ordsSABase + "/config/databases/" + poolName + "/wallet/" if ordssrvs.Spec.PoolSettings[i].PoolWalletSecret == nil { poolWalletVolume := volumeBuild(poolWalletVolumeName, poolWalletVolumeName, "EmptyDir") @@ -835,7 +851,7 @@ func VolumesDefine(ctx context.Context, ordssrvs *dbapi.OrdsSrvs) ([]corev1.Volu if ordssrvs.Spec.PoolSettings[i].TNSAdminSecret != nil { if ordssrvs.Spec.PoolSettings[i].DBWalletSecret == nil { tnsSecretName := ordssrvs.Spec.PoolSettings[i].TNSAdminSecret.SecretName - poolTNSAdminVolumeName := "config-database-pool-network-admin-" + poolName + poolTNSAdminVolumeName := ordssrvs.Name + "-config-database-pool-network-admin-" + poolName if !definedVolumes[poolTNSAdminVolumeName] { poolTNSAdminVolume := volumeBuild(poolTNSAdminVolumeName, tnsSecretName, "Secret") volumes = append(volumes, poolTNSAdminVolume) @@ -1020,13 +1036,19 @@ func (r *OrdsSrvsReconciler) envDefine(ordssrvs *dbapi.OrdsSrvs, initContainer b envVars := []corev1.EnvVar{} + // Central Configuration + if ordssrvs.Spec.CentralConfigUrl != "" { + envVars = addEnvVar(envVars, "central_config_url", ordssrvs.Spec.CentralConfigUrl) + envVars = addEnvVar(envVars, "central_config_wallet", ordssrvs.Spec.CentralConfigWallet) + } + // ORDS_CONFIG ORDS_CONFIG := ordsSABase + "/config" logger.Info("Setting ORDS_CONFIG to " + ORDS_CONFIG) envVars = addEnvVar(envVars, "ORDS_CONFIG", ORDS_CONFIG) // adding info for private key - if passwordEncryption && initContainer { + if r.passwordEncryption && initContainer { passwordKey := ordssrvs.Spec.EncPrivKey.PasswordKey logger.Info("Setting ENC_PRV_KEY env variable to " + passwordKey) envVars = addEnvVar(envVars, "ENC_PRV_KEY", passwordKey) @@ -1047,54 +1069,75 @@ func (r *OrdsSrvsReconciler) envDefine(ordssrvs *dbapi.OrdsSrvs, initContainer b envVars = addEnvVar(envVars, "download_apex", strconv.FormatBool(ordssrvs.Spec.GlobalSettings.APEXDownload)) envVars = addEnvVar(envVars, "download_url_apex", ordssrvs.Spec.GlobalSettings.APEXDownloadUrl) - envVars = addEnvVar(envVars, "external_apex", APEXInstallationExternal) + envVars = addEnvVar(envVars, "external_apex", r.APEXInstallationExternal) // passwords are set for the init container only for i := 0; i < len(ordssrvs.Spec.PoolSettings); i++ { poolName := strings.ReplaceAll(strings.ToLower(ordssrvs.Spec.PoolSettings[i].PoolName), "-", "_") logger.Info("Preparing env for pool " + poolName) - // dbpassword - envName := poolName + "_dbpassword" - // it can be provided by a wallet - if ordssrvs.Spec.PoolSettings[i].DBSecret.SecretName != "" { + // dbconnectiontype + // if set the init container will test the connection + // if not set and provided by Central Configuration, init script will skip the connection test + if ordssrvs.Spec.PoolSettings[i].DBConnectionType != "" { + envVars = addEnvVar(envVars, poolName + "_dbconnectiontype", ordssrvs.Spec.PoolSettings[i].DBConnectionType) + } + + // dbusername + if ordssrvs.Spec.PoolSettings[i].DBUsername != "" { + envVars = addEnvVar(envVars, poolName + "_dbusername", ordssrvs.Spec.PoolSettings[i].DBUsername) + + // dbpassword + envName := poolName + "_dbpassword" + // it can be provided by a wallet + if ordssrvs.Spec.PoolSettings[i].DBSecret.SecretName != "" { secretName := ordssrvs.Spec.PoolSettings[i].DBSecret.SecretName secretKey := ordssrvs.Spec.PoolSettings[i].DBSecret.PasswordKey envVars = r.addSecretEnvVar(envVars, ordssrvs, envName, secretName, secretKey, ctx) - } + } - // dbadminuserpassword - if ordssrvs.Spec.PoolSettings[i].DBAdminUserSecret.SecretName != "" { + } - // autoupgrade only if dbAdminPassword provided + // dbadminuser + if ordssrvs.Spec.PoolSettings[i].DBAdminUser != "" { + envVars = addEnvVar(envVars, poolName + "_dbadminuser", ordssrvs.Spec.PoolSettings[i].DBAdminUser) + // autoupgrade only if dbAdminUser provided envVars = addEnvVar(envVars, poolName+"_autoupgrade_ords", strconv.FormatBool(ordssrvs.Spec.PoolSettings[i].AutoUpgradeORDS)) envVars = addEnvVar(envVars, poolName+"_autoupgrade_apex", strconv.FormatBool(ordssrvs.Spec.PoolSettings[i].AutoUpgradeAPEX)) - envName := poolName + "_dbadminuserpassword" - secretName := ordssrvs.Spec.PoolSettings[i].DBAdminUserSecret.SecretName - secretKey := ordssrvs.Spec.PoolSettings[i].DBAdminUserSecret.PasswordKey - envVars = r.addSecretEnvVar(envVars, ordssrvs, envName, secretName, secretKey, ctx) - } + // dbadminuserpassword + if ordssrvs.Spec.PoolSettings[i].DBAdminUserSecret.SecretName != "" { + envName := poolName + "_dbadminuserpassword" + secretName := ordssrvs.Spec.PoolSettings[i].DBAdminUserSecret.SecretName + secretKey := ordssrvs.Spec.PoolSettings[i].DBAdminUserSecret.PasswordKey + envVars = r.addSecretEnvVar(envVars, ordssrvs, envName, secretName, secretKey, ctx) + } - // dbcdbadminuserpassword - if ordssrvs.Spec.PoolSettings[i].DBCDBAdminUserSecret.SecretName != "" { - envName := poolName + "_dbcdbadminuserpassword" - secretName := ordssrvs.Spec.PoolSettings[i].DBCDBAdminUserSecret.SecretName - secretKey := ordssrvs.Spec.PoolSettings[i].DBCDBAdminUserSecret.PasswordKey - envVars = r.addSecretEnvVar(envVars, ordssrvs, envName, secretName, secretKey, ctx) } + // dbcdbadminuser + if ordssrvs.Spec.PoolSettings[i].DBCDBAdminUser != "" { + envVars = addEnvVar(envVars, poolName + "_dbcdbadminuser", ordssrvs.Spec.PoolSettings[i].DBCDBAdminUser) + + // dbcdbadminuserpassword + if ordssrvs.Spec.PoolSettings[i].DBCDBAdminUserSecret.SecretName != "" { + envName := poolName + "_dbcdbadminuserpassword" + secretName := ordssrvs.Spec.PoolSettings[i].DBCDBAdminUserSecret.SecretName + secretKey := ordssrvs.Spec.PoolSettings[i].DBCDBAdminUserSecret.PasswordKey + envVars = r.addSecretEnvVar(envVars, ordssrvs, envName, secretName, secretKey, ctx) + } + } } } return envVars } -func APEXInstallationVolumeDefine(ctx context.Context, ordssrvs *dbapi.OrdsSrvs) corev1.Volume { +func (r *OrdsSrvsReconciler) APEXInstallationVolumeDefine(ctx context.Context, ordssrvs *dbapi.OrdsSrvs) corev1.Volume { logger := log.FromContext(ctx).WithName("APEXInstallationVolumeDefine") var vs corev1.VolumeSource - if APEXInstallationExternal == "false" { + if r.APEXInstallationExternal == "false" { vs = corev1.VolumeSource{ EmptyDir: &corev1.EmptyDirVolumeSource{}, } @@ -1177,14 +1220,14 @@ func (r *OrdsSrvsReconciler) ConfigMapDelete(ctx context.Context, req ctrl.Reque } for _, configMap := range configMapList.Items { - if configMap.Name == ordssrvsGlobalSettingsConfigMapName || configMap.Name == ordssrvsScriptsConfigMapName { + if configMap.Name == r.ordssrvsGlobalSettingsConfigMapName || configMap.Name == r.ordssrvsScriptsConfigMapName { continue } if _, exists := definedPools[configMap.Name]; !exists { if err := r.Delete(ctx, &configMap); err != nil { return err } - RestartPods = ordssrvs.Spec.ForceRestart + r.RestartPods = ordssrvs.Spec.ForceRestart r.Recorder.Eventf(ordssrvs, corev1.EventTypeNormal, "Delete", "ConfigMap %s Deleted", configMap.Name) } } @@ -1278,6 +1321,7 @@ func (r *OrdsSrvsReconciler) WorkloadDelete(ctx context.Context, req ctrl.Reques **************************************************/ func getLabels(name string) map[string]string { return map[string]string{ + "app": name, "app.kubernetes.io/instance": name, controllerLabelKey: controllerLabelVal, } diff --git a/controllers/database/ordssrvs_ordsconfig.go b/controllers/database/ordssrvs_ordsconfig.go index 98ec70a1..45480208 100644 --- a/controllers/database/ordssrvs_ordsconfig.go +++ b/controllers/database/ordssrvs_ordsconfig.go @@ -71,12 +71,12 @@ func (r *OrdsSrvsReconciler) ConfigMapDefine(ctx context.Context, ordssrvs *dbap var defData map[string]string switch configMapName { - case ordssrvsScriptsConfigMapName: + case r.ordssrvsScriptsConfigMapName: defData = make(map[string]string) defData["ords_init.sh"] = readScript(ctx, "/ordssrvs/ords_init.sh") defData["ords_start.sh"] = readScript(ctx, "/ordssrvs/ords_start.sh") defData["RSADecryptOAEP.java"] = readScript(ctx, "/ordssrvs/RSADecryptOAEP.java") - case ordssrvsGlobalSettingsConfigMapName: + case r.ordssrvsGlobalSettingsConfigMapName: // GlobalConfigMap var defStandaloneAccessLog string if ordssrvs.Spec.GlobalSettings.EnableStandaloneAccessLog { diff --git a/docs/ordsservices/README.md b/docs/ordsservices/README.md index 786a3755..9745e0ea 100644 --- a/docs/ordsservices/README.md +++ b/docs/ordsservices/README.md @@ -18,6 +18,7 @@ The custom RestDataServices resource supports the following configurations as a * Multiple OrdsSrvs resources, each with one database pool * Multiple OrdsSrvs resources, each with multiple database pools* * ORDS and APEX database schemas [automatic installation/upgrade](./autoupgrade.md) +* Deploying ORDS with Central Configuration Server *See [Limitations](#limitations) @@ -34,6 +35,24 @@ OrdsSrvs controller supports the majority of ORDS configuration settings as per Before installing the OrdsSrvs controller, ensure that the Oracle Database Operator (OraOperator) is installed in your Kubernetes environment. Please follow the detailed installation steps provided in the [README](https://github.com/oracle/oracle-database-operator/blob/main/README.md) to complete this process. The OraOperator must be properly configured and running, as OrdsSrvs depends on its services for functionality. +## Common configuration examples + +A few common configuration examples can be used to quickly familiarise yourself with the OrdsSrvs Custom Resource Definition. +The "Conclusion" section of each example highlights specific settings to enable functionality that maybe of interest. + +* [Pre-existing Database](./examples/existing_db.md) +* [Containerised Single Instance Database (SIDB)](./examples/sidb_container.md) +* [Multidatabase using a TNS Names file](./examples/multi_pool.md) +* [Autonomous Database using the OraOperator](./examples/adb_oraoper.md) *See [Limitations](#limitations) +* [Autonomous Database without the OraOperator](./examples/adb.md) +* [Oracle API for MongoDB Support](./examples/mongo_api.md) +* [ORDS and APEX database schemas automatic installation/upgrade](./autoupgrade.md) +* [External tnsnames.ora and Database credentials in an Oracle Wallet](./examples/resources.md) +* [Deploying ORDS with Central Configuration Server](./examples/central_configuration.md) + +Running through all examples in the same Kubernetes cluster illustrates the ability to run multiple ORDS instances with a variety of different configurations. + + ### Namespace Namespace Scoped Deployment For a dedicated namespace deployment of the OrdsSrvs controller, refer to the "Namespace Scoped Deployment" section in the OraOperator [README](https://github.com/oracle/oracle-database-operator/blob/main/README.md#2-namespace-scoped-deployment). @@ -99,26 +118,23 @@ spec: ``` -## Common configuration examples - -A few common configuration examples can be used to quickly familiarise yourself with the OrdsSrvs Custom Resource Definition. -The "Conclusion" section of each example highlights specific settings to enable functionality that maybe of interest. - -* [Pre-existing Database](./examples/existing_db.md) -* [Containerised Single Instance Database (SIDB)](./examples/sidb_container.md) -* [Multidatabase using a TNS Names file](./examples/multi_pool.md) -* [Autonomous Database using the OraOperator](./examples/adb_oraoper.md) *See [Limitations](#limitations) -* [Autonomous Database without the OraOperator](./examples/adb.md) -* [Oracle API for MongoDB Support](./examples/mongo_api.md) -* [ORDS and APEX database schemas automatic installation/upgrade](./autoupgrade.md) -* [External tnsnames.ora and Database credentials in an Oracle Wallet](./examples/resources.md) +## Change Log -Running through all examples in the same Kubernetes cluster illustrates the ability to run multiple ORDS instances with a variety of different configurations. +### Development -## Change Log +### Version 2.1 -### Development +* **Native Kubernetes Secret** +`encPrivKey` attribute is now optional; passwords can be stored in native K8s Secrets without additional encryption. +* **TNSAdminSecret** +adding a dedicated documentation page. see [resources](./docs/ordsservices/examples/resources.md) +* **poolWalletSecret** +New attribute `poolWalletSecret` lets you reference an external Oracle Wallet that contains the database credentials, avoiding storage of the password in a Kubernetes Secret. +see [resources](./docs/ordsservices/examples/resources.md) +* **central.config.url** +Deploying ORDS with Central Configuration Server. +New attribute `central.config.url` (e.g. https://central-config.example.com:8585/central/v1/config) diff --git a/docs/ordsservices/api.md b/docs/ordsservices/api.md index 4268e98c..c84ff7d8 100644 --- a/docs/ordsservices/api.md +++ b/docs/ordsservices/api.md @@ -162,7 +162,17 @@ Deployment or StatefulSet
secretName: string  passwordKey: string Define the private key to decrypt passwords
-true
+false
+ + + +central.config.url
+
+string
+ +Central Configuration Server url (e.g. https://central-config.example.com:8585/central/v1/config) + +false
diff --git a/docs/ordsservices/examples/central_configuration.md b/docs/ordsservices/examples/central_configuration.md new file mode 100644 index 00000000..d4af19e5 --- /dev/null +++ b/docs/ordsservices/examples/central_configuration.md @@ -0,0 +1,340 @@ +# OrdsSrvs Controller: Central Configuration via central.config.url + +This feature introduces support for configuring ORDS instances managed by the OrdsSrvs controller using a central configuration manager. By setting the `central.config.url` attribute, OrdsSrvs retrieves global and pool-specific settings from a central endpoint that implements the ORDS Central Config Manager OpenAPI. + +This document shows: +- A minimal OrdsSrvs example that uses `central.config.url` +- A demo central config manager implemented with Apache HTTPD in Kubernetes +- How to validate the setup and make REST calls against multiple pools + +See ORDS docs: [Configuring additional databases][ords-config-addl-dbs] (tested with 25.3): +https://docs.oracle.com/en/database/oracle/oracle-rest-data-services/25.3/ordig/configuring-additional-databases.html#GUID-EEDA7256-7EDE-467B-B71D-6C7C184D982E + +>Note: This example is for demo/testing only. Do not use plaintext passwords or HTTP in production. + +## Overview + +- Central configuration endpoint: + - Global config: `GET /central/v1/config` + - Pool config: `GET /central/v1/config/pool/{poolName}` +- In this example, the pool is resolved from the URL path using `security.externalMappingPathPrefix = true`. +- Example pools: `pool-a` and `pool-b` +- ORDS base path: `/ords` +- Example REST access: `/ords/{poolName}/...` + +## Prerequisites + +See ORDSSRVS prerequisites: [ORDSSRVS prerequisites](../README.md#prerequisites) + +In addition to the above, you’ll need: +- A reachable Oracle database for the pools +- Target Kubernetes namespace (replace NAMESPACE) + +Security reminder: +>- Use HTTPS/TLS for the central config service and for ORDS in production. +>- Store credentials in Kubernetes Secrets (not ConfigMaps) and avoid plaintext passwords. +>- Restrict network access via NetworkPolicies. + +## Central Config Manager files + +Create these four files locally. We will package them into a ConfigMap. + +- **central-config-httpd.conf** httpd config +- **central-config-global.json** ORDS global configuration +- **central-config-pool-a.json** pool-a configuration +- **central-config-pool-b.json** pool-b configuration + +central-config-httpd.conf: +``` +ServerName localhost +ServerRoot "/usr/local/apache2" +Listen 80 +LoadModule mpm_event_module modules/mod_mpm_event.so +LoadModule authz_core_module modules/mod_authz_core.so +LoadModule dir_module modules/mod_dir.so +LoadModule mime_module modules/mod_mime.so +LoadModule log_config_module modules/mod_log_config.so +LoadModule rewrite_module modules/mod_rewrite.so +LoadModule unixd_module modules/mod_unixd.so +LoadModule alias_module modules/mod_alias.so +User www-data +Group www-data +PidFile /usr/local/apache2/logs/httpd.pid +ErrorLog /proc/self/fd/2 +LogLevel warn +CustomLog /proc/self/fd/1 common +DocumentRoot "/usr/local/apache2/htdocs" + + AllowOverride None + Require all granted + +AddType application/json .json +FileETag MTime Size +RewriteEngine On +RewriteRule ^/health$ /ok.txt [PT,L] +#RewriteCond %{REQUEST_URI} ^/central/ +#RewriteCond %{HTTP:Request-Id} ="" +#RewriteRule ^ - [R=400,L] +RewriteRule ^/central/v1/config$ /global.json [PT,L] +#RewriteRule ^/central/v1/config/pool/localhost /pools/default.json [PT,L] +RewriteRule ^/central/v1/config/pool/([^/]+)$ /pools/$1.json [PT,L] +DirectoryIndex disabled +``` + +central-config-global.json: +``` +{ + "settings": { + "security.externalMappingPathPrefix": true, + "restEnabledSql.active": true, + "feature.sdw": true, + "debug.debugger": "debug", + "standalone.https.port": "8443", + "standalone.http.port": "8080", + "security.forceHTTPS": false, + "standalone.context.path": "/ords" + }, + "links": [ + { + "rel": "collection", + "href": "http://central-config-svc/central/v1/config/" + }, + { + "rel": "self", + "href": "http://central-config-svc/central/v1/config/" + }, + { + "rel": "search", + "href": "http://central-config-svc/central/v1/config/pool/{host}", + "templated": true + } + ] +} +``` + +central-config-pool-a.json: +``` +{ + "database": { + "pool": { + "name": "pool-a", + "settings": { + "db.connectionType": "customurl", + "db.customURL": "jdbc:oracle:thin:@sidb:1521/FREEPDB1", + "db.username": "ORDS_PUBLIC_USER", + "db.password": "" + } + } + } +} +``` + +central-config-pool-b.json: +``` +{ + "database": { + "pool": { + "name": "pool-b", + "settings": { + "db.connectionType": "customurl", + "db.customURL": "jdbc:oracle:thin:@sidb:1521/FREEPDB1", + "db.username": "ORDS_PUBLIC_USER", + "db.password": "" + } + } + } +} +``` + +>Important: Passwords here are for testing only. + +## Create the ConfigMap with the central config content + +``` +kubectl -n NAMESPACE create configmap central-config \ + --from-file=central-config-httpd.conf \ + --from-file=central-config-global.json \ + --from-file=central-config-pool-a.json \ + --from-file=central-config-pool-b.json +``` + +## Deploy the demo central config server (Apache HTTPD) and Service + +central-config-server.template: +``` +apiVersion: apps/v1 +kind: Deployment +metadata: + name: central-config-server + namespace: NAMESPACE +spec: + replicas: 2 + selector: + matchLabels: + app: central-config-server + template: + metadata: + labels: + app: central-config-server + spec: + containers: + - name: httpd + image: docker.io/library/httpd:latest + ports: + - containerPort: 80 + volumeMounts: + - name: httpd-conf + mountPath: /usr/local/apache2/conf/httpd.conf + subPath: httpd.conf + readOnly: true + - name: config + mountPath: /usr/local/apache2/htdocs + readOnly: true + volumes: + - name: httpd-conf + configMap: + name: central-config + items: + - key: central-config-httpd.conf + path: httpd.conf + - name: config + configMap: + name: central-config + items: + - key: central-config-global.json + path: global.json + - key: central-config-pool-a.json + path: pools/pool-a.json + - key: central-config-pool-b.json + path: pools/pool-b.json +--- +apiVersion: v1 +kind: Service +metadata: + name: central-config-svc + namespace: NAMESPACE +spec: + type: ClusterIP + selector: + app: central-config-server + ports: + - port: 80 + targetPort: 80 +``` + +Apply: +``` +kubectl apply -f central-config-server.template +``` + +## Validate the central config endpoints + +from the cluster network: +``` + curl http://central-config-svc/central/v1/config + curl http://central-config-svc/central/v1/config/pool/pool-a + curl http://central-config-svc/central/v1/config/pool/pool-b +``` + +You should receive the JSON documents defined above. + +## Configure OrdsSrvs to use central.config.url + +OrdsSrvs manifest: +``` +apiVersion: database.oracle.com/v4 +kind: OrdsSrvs +metadata: + name: ordssrvs-cc + namespace: NAMESPACE +spec: + image: container-registry.oracle.com/database/ords:latest + central.config.url: http://central-config-svc/central/v1/config + serviceAccountName: ordssrvs-sa +``` + +Apply: +``` +kubectl apply -f ordssrvs-central-config.yaml +``` + +The controller will: +- Fetch the global config from `/central/v1/config` +- Resolve the pool by URL path (because `security.externalMappingPathPrefix` is true) +- Fetch pool configs from `/central/v1/config/pool/{poolName}` as requests arrive + +## Prepare a test schema and object in the database + +Run the following in your database (adjust schema if needed). Example uses schema `ORDSSRVSTESTCASE`. + +``` +CREATE TABLE TESTCASE ( STATUS VARCHAR2 (100)); +TRUNCATE TABLE TESTCASE; +INSERT INTO TESTCASE VALUES ('ORDSSRVS_TESTCASE_CHECK'); + +BEGIN + ORDS.enable_schema( + p_enabled => TRUE, + p_schema => 'ORDSSRVSTESTCASE', + p_url_mapping_type => 'BASE_PATH', + p_url_mapping_pattern => 'ordssrvs_testcase', + p_auto_rest_auth => FALSE + ); +END; +/ + +BEGIN + ORDS.ENABLE_OBJECT( + p_enabled => TRUE, + p_schema => 'ORDSSRVSTESTCASE', + p_object => 'TESTCASE', + p_object_type => 'TABLE', + p_object_alias => 'testcase_table' + ); +END; +/ +``` + +## Test REST calls through ORDS with path-based pool resolution + +Assuming ORDS is listening on 8443 and using the `/ords` context path: + +``` +curl -ik https://ordssrvs-cc:8443/ords/pool-a/ordssrvs_testcase/testcase_table/ -H "Host: localhost" +curl -ik https://ordssrvs-cc:8443/ords/pool-b/ordssrvs_testcase/testcase_table/ -H "Host: localhost" +``` + +- The pool is resolved from the path segment after `/ords/` (pool-a or pool-b). +- There is no server-name-to-pool mapping in this example. + +## Troubleshooting + +- OrdsSrvs not picking up central config: + - Check logs of the OrdsSrvs pods for fetch errors + - Confirm `central.config.url` is reachable via in-cluster DNS + - Validate central config endpoints return HTTP 200 and JSON + +- Pool resolution issues: + - Ensure `security.externalMappingPathPrefix = true` in the global config + - Confirm the path is `/ords/{poolName}/...` + +- Database connectivity: + - Verify `db.customURL`, `db.username`, and credentials for each pool + - Use Secrets for credentials in production + - Check network access from ORDS pods to the database + +- Security: + - Use TLS for the central config service (`https`) and for ORDS + - Align with Oracle internal security/compliance guidelines when deploying third-party images (e.g., httpd) + +## Notes and Best Practices + +- Replace `NAMESPACE`, and `` with your values. +- For production: + - Replace HTTP with HTTPS and configure trusted certificates + - Move credentials to Secrets or wallet-based authentication + - Add health and readiness probes to both central-config and ORDS + - Apply NetworkPolicies to limit ingress/egress + - Implement caching/failover strategies for central config availability + +This example demonstrates how OrdsSrvs can centrally source both global and pool configurations, enabling consistent, scalable ORDS deployments with path-based pool selection. \ No newline at end of file diff --git a/ordssrvs/ords_init.sh b/ordssrvs/ords_init.sh index 8766a882..12d9dac9 100644 --- a/ordssrvs/ords_init.sh +++ b/ordssrvs/ords_init.sh @@ -68,7 +68,7 @@ function global_parameters(){ # APEX_HOME is used only here and it is set on images <= 24.1.x if [[ -n ${APEX_HOME} ]]; then APEX_INSTALL=${APEX_HOME}/${APEX_VER} - echo "WARNING: APEX installation ${APEX_INSTALL}, ORDS image probably older than 24.2" + echo "WARNING: APEX_HOME detected, APEX_HOME:${APEX_HOME}; ORDS image may be older than 24.2" fi APEXINS=${APEX_INSTALL}/apexins.sql APEX_IMAGES=${APEX_INSTALL}/images @@ -77,9 +77,22 @@ function global_parameters(){ sub "global parameters" echo "external_apex : ${external_apex:?}" echo "download_apex : ${download_apex:?}" - echo "download_url_apex : ${download_url_apex:?}" - echo "APEX_INSTALL : $APEX_INSTALL" - echo "APEX_IMAGES : $APEX_IMAGES" + + if [[ ${download_apex} == "true" ]] + then + echo "download_url_apex : ${download_url_apex:?}" + fi + + if [[ ( ${external_apex} == "true" ) || ( ${download_apex} == "true" ) ]] + then + echo "APEX_INSTALL : $APEX_INSTALL" + echo "APEX_IMAGES : $APEX_IMAGES" + fi + + if [[ -n ${central_config_url} ]] + then + echo "central_config_url : ${central_config_url}" + fi # check for password encryption if [[ -n "${ENC_PRV_KEY}" ]]; then @@ -93,11 +106,12 @@ function global_parameters(){ #------------------------------------------------------------------------------ prepare_pool_connect_string() { - local -n _conn_string="${1}" + local -n _x_conn_string="${1}" + local -r _user="${2%%/ *}" + local -r _pwd="${3}" - sub "Prepare connect string" + sub "Prepare connect string for user ${_user}" - local -r _admin_user=$($ords_cfg_cmd get --secret db.adminUser | tail -1) local _conn_type _conn_type=$($ords_cfg_cmd get db.connectionType |tail -1) if [[ $_conn_type == "customurl" ]]; then @@ -125,9 +139,9 @@ prepare_pool_connect_string() { if [[ -n ${_conn} ]]; then echo "Connection String (${_conn_type}): ${_conn}" - _conn_string="${_admin_user%%/ *}/${config["dbadminuserpassword"]}@${_conn}" - if [[ ${_admin_user%%/ *} == "SYS" ]]; then - _conn_string="${_conn_string=} AS SYSDBA" + _x_conn_string="${_user}/${_pwd}@${_conn}" + if [[ ${_user} == "SYS" ]]; then + _x_conn_string="${_x_conn_string=} AS SYSDBA" fi fi } @@ -171,6 +185,8 @@ function run_sql { fi echo "Running SQL" + #DEBUG + #echo ${_conn_string} # NOTE to maintainer; the heredoc must be TAB indented _output=$(sql -S -nohistory -noupdates /nolog <<-EOSQL @@ -243,7 +259,7 @@ function create_adb_user() { local _config_user _config_user=$($ords_cfg_cmd get db.username | tail -1) - if [[ -z ${_config_user} ]] || [[ ${_config_user} == "ORDS_PUBLIC_USER" ]]; then + if [[ -z "${_config_user}" ]] || [[ "${_config_user}" == "ORDS_PUBLIC_USER" ]]; then echo "FATAL: You must specify a db.username <> ORDS_PUBLIC_USER in pool \"${_pool_name}\"" dump_stack return 1 @@ -257,11 +273,11 @@ function create_adb_user() { BEGIN SELECT USERNAME INTO l_user FROM DBA_USERS WHERE USERNAME='${_config_user}'; EXECUTE IMMEDIATE 'ALTER USER \"${_config_user}\" PROFILE ORA_APP_PROFILE'; - EXECUTE IMMEDIATE 'ALTER USER \"${_config_user}\" IDENTIFIED BY \"${config["dbpassword"]}\"'; + EXECUTE IMMEDIATE 'ALTER USER \"${_config_user}\" IDENTIFIED BY \"${config[dbpassword]}\"'; DBMS_OUTPUT.PUT_LINE('${_config_user} Exists - Password reset'); EXCEPTION WHEN NO_DATA_FOUND THEN - EXECUTE IMMEDIATE 'CREATE USER \"${_config_user}\" IDENTIFIED BY \"${config["dbpassword"]}\" PROFILE ORA_APP_PROFILE'; + EXECUTE IMMEDIATE 'CREATE USER \"${_config_user}\" IDENTIFIED BY \"${config[dbpassword]}\" PROFILE ORA_APP_PROFILE'; DBMS_OUTPUT.PUT_LINE('${_config_user} Created'); END; EXECUTE IMMEDIATE 'GRANT CONNECT TO \"${_config_user}\"'; @@ -364,9 +380,21 @@ set_ords_secret() { } #------------------------------------------------------------------------------ -read_passwords(){ +setup_credentials(){ - sub "Reading passwords" + + sub "Setup Credentials" + + # reading users from env + for key in dbusername dbadminuser dbcdbadminuser dbconnectiontype; do + var_key="${pool_name_underscore}_${key}" + var_val="${!var_key}" + if [[ (-n "${var_val}") ]]; then + config[${key}]="${var_val}" + fi + done + + # reading passwords from env and eventually decrypting for key in dbpassword dbadminuserpassword dbcdbadminuserpassword; do var_key="${pool_name_underscore}_${key}" echo "Obtaining value from initContainer variable: ${var_key}" @@ -375,17 +403,22 @@ read_passwords(){ echo "Decrypting ${var_key}" cd /opt/oracle/ords/scripts || return var_val_enc=${var_val} - var_val=$(java RSADecryptOAEP "${ENC_PRV_KEY}" "${var_val_enc}") - fi - config[${key}]="${var_val}" + var_val=$(java RSADecryptOAEP "${ENC_PRV_KEY}" "${var_val_enc}") + fi + + if [[ (-n "${var_val}") ]]; then + config[${key}]="${var_val}" + fi done # Set ORDS Secrets - set_ords_secret "${pool_name}" "db.password" "${config["dbpassword"]}" + echo "Saving ORDS credentials" + local -i rc=0 + set_ords_secret "${pool_name}" "db.password" "${config[dbpassword]}" rc=$((rc + $?)) - set_ords_secret "${pool_name}" "db.adminUser.password" "${config["dbadminuserpassword"]}" + set_ords_secret "${pool_name}" "db.adminUser.password" "${config[dbadminuserpassword]}" rc=$((rc + $?)) - set_ords_secret "${pool_name}" "db.cdb.adminUser.password" "${config["dbcdbadminuserpassword"]}" + set_ords_secret "${pool_name}" "db.cdb.adminUser.password" "${config[dbcdbadminuserpassword]}" rc=$((rc + $?)) if (( rc > 0 )); then @@ -399,30 +432,28 @@ read_passwords(){ #------------------------------------------------------------------------------ ords_upgrade() { local -r _pool_name="${1}" - local -r _upgrade_key="${2}" local -i _rc=0 sub "ORDS install/upgrade" - if [[ -n "${config["dbadminuserpassword"]}" ]]; then - # Get usernames - local -r ords_user=$($ords_cfg_cmd get db.username | tail -1) - local -r ords_admin=$($ords_cfg_cmd get db.adminUser | tail -1) + if [[ -n "${config[dbadminuser]}" ]]; then - echo "Performing ORDS install/upgrade as $ords_admin into $ords_user on pool \"${_pool_name}\"" + echo "Performing ORDS install/upgrade as ${config[dbadminuser]} into ${config[dbusername]} on pool ${_pool_name}" if [[ ${_pool_name} == "default" ]]; then ords --config "$ORDS_CONFIG" install --db-only \ - --admin-user "$ords_admin" --password-stdin <<< "${config["dbadminuserpassword"]}" + --admin-user "${config[dbadminuser]}" --password-stdin <<< "${config["dbadminuserpassword"]}" _rc=$? else ords --config "$ORDS_CONFIG" install --db-pool "${_pool_name}" --db-only \ - --admin-user "$ords_admin" --password-stdin <<< "${config["dbadminuserpassword"]}" + --admin-user "${config[dbadminuser]}" --password-stdin <<< "${config["dbadminuserpassword"]}" _rc=$? fi # Dar be bugs below deck with --db-user so using the above # ords --config "$ORDS_CONFIG" install --db-pool "$1" --db-only \ # --admin-user "$ords_admin" --db-user "$ords_user" --password-stdin <<< "${!2}" + else + echo "WARNING: Admin user not set, skipping ORDS install/upgare" fi return $_rc @@ -583,7 +614,7 @@ function apex_upgrade() { sub "APEX Installation/Upgrade" - if [[ -z ${APEX_INSTALL} ]]; then + if [[ -z "${APEX_INSTALL}" ]]; then echo "ERROR: APEX_INSTALL not set" return 1 fi @@ -611,21 +642,21 @@ function apex_upgrade() { function apex_housekeeping(){ # check database APEX version regardless of APEX parameters - get_apex_version "${conn_string}" "db_apex_version" - if [[ -z ${db_apex_version} ]]; then + get_apex_version "${admin_conn_string}" "db_apex_version" + if [[ -z "${db_apex_version}" ]]; then echo "FATAL: Unable to get APEX Version for pool \"${pool_name}\"" return 1 fi # check if apex upgrade is enabled - if [[ ${apex_upgrade} != "true" ]]; then + if [[ "${apex_upgrade}" != "true" ]]; then echo "APEX Install/Upgrade not requested for pool \"${pool_name}\"" return 0 fi # get suggested action - get_apex_action "${conn_string}" "${db_apex_version}" "db_apex_action" - if [[ -z ${db_apex_action} ]]; then + get_apex_action "${admin_conn_string}" "${db_apex_version}" "db_apex_action" + if [[ -z "${db_apex_action}" ]]; then echo "FATAL: Unable to get APEX suggested action for pool \"${pool_name}\"" return 1 fi @@ -634,7 +665,7 @@ function apex_housekeeping(){ echo "APEX version : \"${db_apex_version}\"" echo "APEX suggested action : $db_apex_action" if [[ ${db_apex_action} != "none" ]]; then - apex_upgrade "${conn_string}" "${pool_name_underscore}_autoupgrade_apex" + apex_upgrade "${admin_conn_string}" "${pool_name_underscore}_autoupgrade_apex" rc=$? if (( rc > 0 )); then echo "FATAL: Unable to ${db_apex_action} APEX for pool \"${pool_name}\"" @@ -653,160 +684,229 @@ function pool_parameters(){ sub "pool parameters" apex_upgrade_var=${pool_name_underscore}_autoupgrade_apex apex_upgrade=${!apex_upgrade_var} - [[ -z $apex_upgrade ]] && apex_upgrade=false + [[ -z "$apex_upgrade" ]] && apex_upgrade=false echo "${pool_name} - autoupgrade_apex : ${apex_upgrade}" ords_upgrade_var=${pool_name_underscore}_autoupgrade_ords ords_upgrade=${!ords_upgrade_var} - [[ -z $ords_upgrade ]] && ords_upgrade=false + [[ -z "$ords_upgrade" ]] && ords_upgrade=false echo "${pool_name} - autoupgrade_ords : ${ords_upgrade}" -} + pool_ords_wallet_path="${ORDS_CONFIG}/databases/${pool_name}/wallet" + pool_ords_wallet="${pool_ords_wallet_path}/cwallet.sso" +} -#------------------------------------------------------------------------------ -# INIT -#------------------------------------------------------------------------------ -declare -A pool_exit -sep -sub "ORDSSRVS init" -# FIXME remove -env|sort +pool_admin_setup(){ -sep -global_parameters -ords_client_version -apex_download -apex_external + sub "Admin Setup" + # dbadminuserpassword + if [[ (-z "${config[dbadminuserpassword]}") && (-f "${pool_ords_wallet}") ]] + then + ls -l "${pool_ords_wallet}" + echo "Admin password not set and wallet exists, reading admin password from wallet" + PKILIB="/opt/oracle/sqlcl/lib/oraclepki.jar" + PKICLASS="oracle.security.pki.OracleSecretStoreTextUI" + PKIENTRY="db.adminUser.password" + config[dbadminuserpassword]=$(java -cp ${PKILIB} ${PKICLASS} -wrl ${pool_ords_wallet_path} -viewEntry ${PKIENTRY}|grep ${PKIENTRY}|cut -f2 -d\=|cut -f2 -d\ ) + fi -# check APEX installation files version, downloaded or mounted by PVC -check_apex_installation_version + prepare_pool_connect_string "admin_conn_string" "${config[dbadminuser]}" "${config[dbadminuserpassword]}" + if [[ -z "${admin_conn_string}" ]]; then + echo "Unable to get admin database connect string for pool \"${pool_name}\"" + dump_stack + pool_exit[${pool_name}]=1 + continue + fi -for pool in "$ORDS_CONFIG"/databases/*; do - rc=0 - pool_name=$(basename "$pool") - pool_name_underscore=${pool_name//-/_} - pool_exit[${pool_name}]=0 - ords_cfg_cmd="ords --config $ORDS_CONFIG config --db-pool ${pool_name}" - declare -A config + is_adb=false + check_adb "${admin_conn_string}" "is_adb" + rc=$? + if (( rc > 0 )); then + pool_exit[${pool_name}]=1 + continue + fi - sep - sub "Pool ${pool_name}" - echo "poolName: ${pool_name}" + if (( is_adb )); then + # Create ORDS User + echo "Processing ADB in Pool: ${pool_name}" + create_adb_user "${admin_conn_string}" "${pool_name}" + continue + fi - pool_parameters + # not ADB - read_passwords + # APEX + apex_housekeeping rc=$? if (( rc > 0 )); then - pool_exit[${pool_name}]=1 - continue - fi + echo "FATAL: unable to manage APEX configuration for pool \"${pool_name}\"" + dump_stack + pool_exit[${pool_name}]=1 + continue + fi - sub "SQL setup" - pool_ords_wallet_path="${ORDS_CONFIG}/databases/${pool_name}/wallet" - pool_ords_wallet="${pool_ords_wallet_path}/cwallet.sso" - if [[ (-z ${config["dbadminuserpassword"]}) && (-f "${pool_ords_wallet}") ]] - then - ls -l "${pool_ords_wallet}" + # database ORDS + if [[ ${ords_upgrade} == "true" ]]; then + ords_upgrade "${pool_name}" + rc=$? + if (( rc > 0 )); then + echo "FATAL: Unable to perform requested ORDS install/upgrade on pool \"${pool_name}\"" + pool_exit[${pool_name}]=1 + dump_stack + fi + else + echo "ORDS Install/Upgrade not requested for pool \"${pool_name}\"" + fi +} - # FIXME evaluate to skip sql check or extract admin password from wallet - - #echo "Admin password not set and wallet already exists, ignoring sql setup" - #pool_exit[${pool_name}]=0 - #continue +pool_check(){ - echo "Admin password not set and wallet exists, reading admin password from wallet" + sub "SQL Test" + + # dbpassword from wallet + if [[ (-z "${config[dbpassword]}" ) && (-f "${pool_ords_wallet}") ]] + then + ls -l "${pool_ords_wallet}" + echo "Password not set and wallet exists, reading password from wallet" PKILIB="/opt/oracle/sqlcl/lib/oraclepki.jar" PKICLASS="oracle.security.pki.OracleSecretStoreTextUI" - PKIENTRY="db.adminUser.password" - config["dbadminuserpassword"]=$(java -cp ${PKILIB} ${PKICLASS} -wrl ${pool_ords_wallet_path} -viewEntry ${PKIENTRY}|grep ${PKIENTRY}|cut -f2 -d\=|cut -f2 -d\ ) + PKIENTRY="db.password" + config["dbpassword"]=$(java -cp ${PKILIB} ${PKICLASS} -wrl ${pool_ords_wallet_path} -viewEntry ${PKIENTRY}|grep ${PKIENTRY}|cut -f2 -d\=|cut -f2 -d\ ) fi - if [[ (-z ${config["dbadminuserpassword"]}) ]] + if [[ (-z ${config[dbpassword]} ) ]] then - echo "ERROR: admin password not set, aborting sql setup" + echo "ERROR: password not set, aborting sql check" pool_exit[${pool_name}]=1 continue fi - prepare_pool_connect_string "conn_string" - if [[ -z ${conn_string} ]]; then + prepare_pool_connect_string "ords_conn_string" "${config[dbusername]}" "${config[dbpassword]}" + if [[ -z ${ords_conn_string} ]]; then echo "Unable to get database connect string for pool \"${pool_name}\"" dump_stack pool_exit[${pool_name}]=1 continue fi - setup_sql_environment - rc=$? - if (( rc > 0 )); then - pool_exit[${pool_name}]=1 - continue - fi - - sub "SQL Test" - if [[ (-n ${conn_string}) || (-n ${_cloudconfig}) ]] + if [[ (-n ${ords_conn_string}) || (-n ${_cloudconfig}) ]] then + # DEBUG + # echo ${ords_conn_string} sql /nolog 2>&1 <<-SQL set lines 1000 pages 100 feed off WHENEVER SQLERROR EXIT SQL.ERROR WHENEVER OSERROR EXIT 1 ${_cloudconfig} - connect ${conn_string} + connect ${ords_conn_string} alter session set nls_date_format='dd/mm/yyyy hh24:mi:ss'; - SELECT sysdate, host_name, user, instance_name, SYS_CONTEXT('USERENV', 'CON_NAME') AS current_pdb FROM V\$instance; + SELECT + SYSDATE, + SYS_CONTEXT('USERENV','SERVER_HOST') AS server_host, + USER, + SYS_CONTEXT('USERENV','INSTANCE_NAME') AS instance_name, + SYS_CONTEXT('USERENV','CON_NAME') AS current_pdb + FROM dual; + prompt SQL rc=$? echo if (( rc > 0 )); then - echo "ERROR: SQL Test FAILED, sql exit code ${rc}" - pool_exit[${pool_name}]=1 - continue + echo "ERROR: SQL Test FAILED, sql exit code ${rc}" + exit $rc fi else echo "" fi +} - is_adb=false - check_adb "${conn_string}" "is_adb" - rc=$? - if (( rc > 0 )); then - pool_exit[${pool_name}]=1 - continue - fi +#------------------------------------------------------------------------------ +# INIT +#------------------------------------------------------------------------------ +declare -A pool_exit +sep +sub "ORDSSRVS init" +sep - if (( is_adb )); then - # Create ORDS User - echo "Processing ADB in Pool: ${pool_name}" - create_adb_user "${conn_string}" "${pool_name}" - continue - fi +global_parameters +ords_client_version - # not ADB +apex_download +apex_external - # APEX - apex_housekeeping +# check APEX installation files version, downloaded or mounted by PVC +check_apex_installation_version + +if [[ !( -d "${ORDS_CONFIG}/databases/" ) ]] +then + echo "No database pools found under ${ORDS_CONFIG}/databases" + echo "init end" + exit 0 +fi + +for pool in "${ORDS_CONFIG}/databases/"*; do + rc=0 + pool_name=$(basename "$pool") + pool_name_underscore=${pool_name//-/_} + pool_exit[${pool_name}]=0 + ords_cfg_cmd="ords --config $ORDS_CONFIG config --db-pool ${pool_name}" + declare -A config=() + + sep + sub "Pool ${pool_name}" + echo "poolName: ${pool_name}" + + pool_parameters + + setup_credentials rc=$? if (( rc > 0 )); then - echo "FATAL: unable to manage APEX configuration for pool \"${pool_name}\"" - dump_stack - pool_exit[${pool_name}]=1 + pool_exit[${pool_name}]=1 + continue + fi + + setup_sql_environment + rc=$? + if (( rc > 0 )); then + pool_exit[${pool_name}]=1 + continue + fi + + sub "Pool Admin setup" + echo "pool : ${pool_name}" + echo "dbadminuser : ${config[dbadminuser]}" + echo "dbconnectiontype: ${config[dbconnectiontype]}" + if [[ (-n "${config[dbadminuser]}") && (-n "${config[dbconnectiontype]}") ]] + then + pool_admin_setup + rc=$? + if (( rc > 0 )); then + pool_exit[${pool_name}]=1 continue + fi + else + echo "Admin user or connection type not set, skipping admin setup" fi - # database ORDS - if [[ ${ords_upgrade} == "true" ]]; then - ords_upgrade "${pool_name}" "${pool_name_underscore}_autoupgrade_ords" - rc=$? - if (( rc > 0 )); then - echo "FATAL: Unable to perform requested ORDS install/upgrade on pool \"${pool_name}\"" - pool_exit[${pool_name}]=1 - dump_stack - fi + sub "Pool Connection check" + echo "pool : ${pool_name}" + echo "dbusername : ${config[dbusername]}" + echo "dbconnectiontype: ${config[dbconnectiontype]}" + if [[ (-n "${config[dbusername]}") && (-n "${config[dbconnectiontype]}") ]] + then + pool_check + rc=$? + if (( rc > 0 )); then + pool_exit[${pool_name}]=1 + continue + fi else - echo "ORDS Install/Upgrade not requested for pool \"${pool_name}\"" + echo "Database user or connection type not set, skipping connection check" fi + + + done sep @@ -819,4 +919,9 @@ for key in "${!pool_exit[@]}"; do fi done +# DEBUG +#echo Sleeping forever +#tail -f /dev/null + +echo "init end" exit $rc diff --git a/ordssrvs/ords_start.sh b/ordssrvs/ords_start.sh index 7959088f..a8c9691a 100644 --- a/ordssrvs/ords_start.sh +++ b/ordssrvs/ords_start.sh @@ -4,6 +4,16 @@ date +"%Y-%m-%d %H:%M:%S" echo "=== ORDS start ===" echo "ORDS_CONFIG: ${ORDS_CONFIG}" +if [[ -n "${central_config_url-}" ]]; then + echo "Starting ORDS using Central Config" + echo "central_config_url : ${central_config_url}" + echo "central_config_wallet : ${central_config_wallet}" + #ords --java-options "-Dconfig.url=${central_config_url} -Dconfig.wallet=${central_config_wallet}" serve + unset ORDS_CONFIG + ords --java-options "-Dconfig.url=${central_config_url}" serve + exit $? +fi + unset APEX_IMAGES # old path, until ORDS image 24.1.1 From 183e9f3237463fb3616959e142c9d070b3ab7019 Mon Sep 17 00:00:00 2001 From: marcstef Date: Mon, 15 Dec 2025 13:58:57 +0000 Subject: [PATCH 4/4] ZipWalletsSecretName, a secret with multiple wallet.zip files --- apis/database/v4/ordssrvs_types.go | 6 +- controllers/database/ordssrvs_controller.go | 30 ++- controllers/database/ordssrvs_ordsconfig.go | 3 +- ordssrvs/ords_init.sh | 207 ++++++++++---------- 4 files changed, 133 insertions(+), 113 deletions(-) diff --git a/apis/database/v4/ordssrvs_types.go b/apis/database/v4/ordssrvs_types.go index 22f6b7a5..e7df2ab9 100644 --- a/apis/database/v4/ordssrvs_types.go +++ b/apis/database/v4/ordssrvs_types.go @@ -92,6 +92,9 @@ type OrdsSrvsSpec struct { // Central Configuration Wallet CentralConfigWallet string `json:"central.config.wallet,omitempty"` + // Specifies the Secret containing one or more wallet.zip archives (whit different names) containing connection details and credentials for the pools. + ZipWalletsSecretName string `json:"zipWalletsSecretName,omitempty"` + } type GlobalSettings struct { @@ -366,10 +369,9 @@ type PoolSettings struct { AutoUpgradeAPEX bool `json:"autoUpgradeAPEX,omitempty"` // Specifies the name of the database user for the connection. - // For non-ADB this will default to ORDS_PUBLIC_USER // For ADBs this must be specified and not ORDS_PUBLIC_USER // If ORDS_PUBLIC_USER is specified for an ADB, the workload will fail - //+kubebuilder:default:="ORDS_PUBLIC_USER" + // db.username can be empty in case of SEPS zip wallets (Secure External Password Store, mkstore credentials, connect /@TNSALIAS) DBUsername string `json:"db.username,omitempty"` // Specifies the Secret with the db password diff --git a/controllers/database/ordssrvs_controller.go b/controllers/database/ordssrvs_controller.go index 6cdd3295..fd60b66a 100644 --- a/controllers/database/ordssrvs_controller.go +++ b/controllers/database/ordssrvs_controller.go @@ -229,7 +229,7 @@ func (r *OrdsSrvsReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c definedPools := make(map[string]bool) for i := 0; i < len(ordssrvs.Spec.PoolSettings); i++ { poolName := strings.ToLower(ordssrvs.Spec.PoolSettings[i].PoolName) - poolConfigMapName := ordssrvs.Name + "-config-database-pool-" + poolName + poolConfigMapName := ordssrvs.Name + "-cfg-pool-" + poolName if definedPools[poolConfigMapName] { return ctrl.Result{}, errors.New("poolName: " + poolName + " is not unique") } @@ -771,6 +771,16 @@ func (r *OrdsSrvsReconciler) VolumesDefine(ctx context.Context, ordssrvs *dbapi. volumeMounts = append(volumeMounts, apexInstallationVolumeMount) } + if ordssrvs.Spec.ZipWalletsSecretName != "" { + secretName := ordssrvs.Spec.ZipWalletsSecretName + logger.Info("ZipWalletsSecretName : "+secretName) + globalCertVolume := volumeBuild(secretName, secretName, "Secret") + globalCertVolumeMount := volumeMountBuild(secretName, ordsSABase+"/zipwallets/", true) + + volumes = append(volumes, globalCertVolume) + volumeMounts = append(volumeMounts, globalCertVolumeMount) + } + // Build volume specifications for globalSettings standaloneVolume := volumeBuild("standalone", "", "EmptyDir") standaloneVolumeMount := volumeMountBuild("standalone", ordsSABase+"/config/global/standalone/", false) @@ -796,7 +806,7 @@ func (r *OrdsSrvsReconciler) VolumesDefine(ctx context.Context, ordssrvs *dbapi. if ordssrvs.Spec.GlobalSettings.CertSecret != nil { secretName := ordssrvs.Spec.GlobalSettings.CertSecret.SecretName globalCertVolume := volumeBuild(secretName, secretName, "Secret") - globalCertVolumeMount := volumeMountBuild(ordssrvs.Spec.GlobalSettings.CertSecret.SecretName, ordsSABase+"/config/certficate/", true) + globalCertVolumeMount := volumeMountBuild(secretName, ordsSABase+"/config/certficate/", true) volumes = append(volumes, globalCertVolume) volumeMounts = append(volumeMounts, globalCertVolumeMount) @@ -809,16 +819,17 @@ func (r *OrdsSrvsReconciler) VolumesDefine(ctx context.Context, ordssrvs *dbapi. poolName := strings.ToLower(ordssrvs.Spec.PoolSettings[i].PoolName) // /opt/oracle/sa/config/databases/POOL/ - poolConfigName := ordssrvs.Name+"-config-database-pool-" + poolName + poolConfigName := ordssrvs.Name+"-cfg-pool-" + poolName poolConfigVolume := volumeBuild(poolConfigName, poolConfigName, "ConfigMap") poolConfigVolumeMount := volumeMountBuild(poolConfigName, ordsSABase+"/config/databases/"+poolName+"/", true) volumes = append(volumes, poolConfigVolume) volumeMounts = append(volumeMounts, poolConfigVolumeMount) // PoolWalletSecret -> /opt/oracle/sa/config/databases/POOL/wallet/ - poolWalletVolumeName := ordssrvs.Name+"-config-database-pool-wallet-" + poolName + poolWalletVolumeName := ordssrvs.Name+"-pool-wallet-" + poolName poolWalletVolumePath := ordsSABase + "/config/databases/" + poolName + "/wallet/" - if ordssrvs.Spec.PoolSettings[i].PoolWalletSecret == nil { + + if ( ( ordssrvs.Spec.PoolSettings[i].PoolWalletSecret == nil ) ){ poolWalletVolume := volumeBuild(poolWalletVolumeName, poolWalletVolumeName, "EmptyDir") poolWalletVolumeMount := volumeMountBuild(poolWalletVolumeName, poolWalletVolumePath, false) volumes = append(volumes, poolWalletVolume) @@ -837,7 +848,7 @@ func (r *OrdsSrvsReconciler) VolumesDefine(ctx context.Context, ordssrvs *dbapi. // DBWalletSecret -> /opt/oracle/sa/config/databases/POOL/network/admin/ if ordssrvs.Spec.PoolSettings[i].DBWalletSecret != nil { dbWalletSecretName := ordssrvs.Spec.PoolSettings[i].DBWalletSecret.SecretName - volumeName := "config-database-pool-network-admin-dbwallet-" + poolName + volumeName := ordssrvs.Name + "-pool-zipwallet-" + poolName if !definedVolumes[volumeName] { poolDBWalletVolume := volumeBuild(volumeName, dbWalletSecretName, "Secret") volumes = append(volumes, poolDBWalletVolume) @@ -851,7 +862,7 @@ func (r *OrdsSrvsReconciler) VolumesDefine(ctx context.Context, ordssrvs *dbapi. if ordssrvs.Spec.PoolSettings[i].TNSAdminSecret != nil { if ordssrvs.Spec.PoolSettings[i].DBWalletSecret == nil { tnsSecretName := ordssrvs.Spec.PoolSettings[i].TNSAdminSecret.SecretName - poolTNSAdminVolumeName := ordssrvs.Name + "-config-database-pool-network-admin-" + poolName + poolTNSAdminVolumeName := ordssrvs.Name + "-pool-netadmin-" + poolName if !definedVolumes[poolTNSAdminVolumeName] { poolTNSAdminVolume := volumeBuild(poolTNSAdminVolumeName, tnsSecretName, "Secret") volumes = append(volumes, poolTNSAdminVolume) @@ -1083,6 +1094,11 @@ func (r *OrdsSrvsReconciler) envDefine(ordssrvs *dbapi.OrdsSrvs, initContainer b envVars = addEnvVar(envVars, poolName + "_dbconnectiontype", ordssrvs.Spec.PoolSettings[i].DBConnectionType) } + // dbWalletZip + if ordssrvs.Spec.PoolSettings[i].DBConnectionType == "" && ordssrvs.Spec.PoolSettings[i].DBWalletZipService != "" { + envVars = addEnvVar(envVars, poolName + "_dbconnectiontype", "dbWalletZip") + } + // dbusername if ordssrvs.Spec.PoolSettings[i].DBUsername != "" { envVars = addEnvVar(envVars, poolName + "_dbusername", ordssrvs.Spec.PoolSettings[i].DBUsername) diff --git a/controllers/database/ordssrvs_ordsconfig.go b/controllers/database/ordssrvs_ordsconfig.go index 45480208..b7db0222 100644 --- a/controllers/database/ordssrvs_ordsconfig.go +++ b/controllers/database/ordssrvs_ordsconfig.go @@ -168,7 +168,8 @@ func (r *OrdsSrvsReconciler) ConfigMapDefine(ctx context.Context, ordssrvs *dbap "pool.xml": fmt.Sprint(`` + "\n" + `` + "\n" + `` + "\n" + - ` ` + ordssrvs.Spec.PoolSettings[poolIndex].DBUsername + `` + "\n" + + //` ` + ordssrvs.Spec.PoolSettings[poolIndex].DBUsername + `` + "\n" + + conditionalEntry("db.username", ordssrvs.Spec.PoolSettings[poolIndex].DBUsername) + conditionalEntry("db.adminUser", ordssrvs.Spec.PoolSettings[poolIndex].DBAdminUser) + conditionalEntry("db.cdb.adminUser", ordssrvs.Spec.PoolSettings[poolIndex].DBCDBAdminUser) + conditionalEntry("apex.security.administrator.roles", ordssrvs.Spec.PoolSettings[poolIndex].ApexSecurityAdministratorRoles) + diff --git a/ordssrvs/ords_init.sh b/ordssrvs/ords_init.sh index 12d9dac9..bd4cead1 100644 --- a/ordssrvs/ords_init.sh +++ b/ordssrvs/ords_init.sh @@ -107,13 +107,18 @@ function global_parameters(){ #------------------------------------------------------------------------------ prepare_pool_connect_string() { local -n _x_conn_string="${1}" - local -r _user="${2%%/ *}" - local -r _pwd="${3}" + local -r _conn_type="${2}" + local -r _user="${3%%/ *}" + local -r _pwd="${4}" - sub "Prepare connect string for user ${_user}" + sub "connect string" + if [[ -n "${_user}" ]] + then + echo "username : ${_user}" + else + echo "username : / (SEPS)" + fi - local _conn_type - _conn_type=$($ords_cfg_cmd get db.connectionType |tail -1) if [[ $_conn_type == "customurl" ]]; then local -r _conn=$($ords_cfg_cmd get db.customURL | tail -1) elif [[ $_conn_type == "tns" ]]; then @@ -130,19 +135,20 @@ prepare_pool_connect_string() { local -r _conn=${_host}:${_port}/${_service:-$_sid} fi fi - else - # db wallet zip, zip file in network/admin, DbWalletSecret - _conn_type="dbWalletZip" - local -r _wallet_service=$($ords_cfg_cmd get db.wallet.zip.service | tail -1) - local -r _conn=${_wallet_service} + elif [[ $_conn_type == "dbWalletZip" ]]; then + local -r _zip_wallet_service=$($ords_cfg_cmd get db.wallet.zip.service | tail -1) + local -r _conn="${_zip_wallet_service}" + else + echo "Empty or unknown connectionType" + return 1 fi - if [[ -n ${_conn} ]]; then - echo "Connection String (${_conn_type}): ${_conn}" - _x_conn_string="${_user}/${_pwd}@${_conn}" - if [[ ${_user} == "SYS" ]]; then - _x_conn_string="${_x_conn_string=} AS SYSDBA" - fi + if [[ ( -n ${_conn} ) ]]; then + echo "connect string : ${_conn}" + _x_conn_string="${_user}/${_pwd}@${_conn}" + if [[ ${_user} == "SYS" ]]; then + _x_conn_string="${_x_conn_string=} AS SYSDBA" + fi fi } @@ -160,13 +166,11 @@ function setup_sql_environment(){ fi ## Get ADB Wallet - #echo "Checking db.wallet.zip.path" - #echo "$ords_cfg_cmd get db.wallet.zip.path" + # echo "Checking db.wallet.zip.path" local -r _wallet_zip_path=$($ords_cfg_cmd get db.wallet.zip.path 2>&1| tail -1) - #echo "wallet_zip_path : \"${_wallet_zip_path}\"" if [[ ! $_wallet_zip_path =~ "Cannot get setting" ]]; then - echo "Using: set cloudconfig ${_wallet_zip_path}" - local -r _cloudconfig="set cloudconfig ${_wallet_zip_path}" + config[cloudconfig]="${_wallet_zip_path}" + echo "db.wallet.zip.path is set, using : set cloudconfig ${config[cloudconfig]}" fi return 0 @@ -174,28 +178,28 @@ function setup_sql_environment(){ } function run_sql { - local -r _conn_string="${1}" - local -r _sql="${2}" - local -n _output="${3}" + local -r _sql="${1}" + local -n _output="${2}" + local -r _heading="${3-off}" local -i _rc=0 if [[ -z ${_sql} ]]; then dump_stack - echo "FATAL: missing SQL calling run_sql" && exit 1 + echo "FATAL: missing SQL calling run\_sql" && exit 1 fi - echo "Running SQL" - #DEBUG - #echo ${_conn_string} + #echo "DEBUG Running SQL" + #echo "DEBUG set cloudconfig ${config[cloudconfig]}" + #echo "DEBUG connect ${config[connect]}" # NOTE to maintainer; the heredoc must be TAB indented _output=$(sql -S -nohistory -noupdates /nolog <<-EOSQL WHENEVER SQLERROR EXIT 1 WHENEVER OSERROR EXIT 1 - ${_cloudconfig} - connect $_conn_string + set cloudconfig ${config[cloudconfig]} + connect ${config[connect]} set serveroutput on echo off pause off feedback off - set heading off wrap off linesize 1000 pagesize 0 + set heading ${_heading} wrap off linesize 1000 pagesize 0 SET TERMOUT OFF VERIFY OFF ${_sql} exit; @@ -213,8 +217,7 @@ function run_sql { #------------------------------------------------------------------------------ function check_adb() { - local -r _conn_string=$1 - local -n _is_adb=$2 + local -n _is_adb=$1 sub "ADB check" @@ -234,7 +237,7 @@ function check_adb() { END; /" echo "Checking if Database is an ADB" - run_sql "${_conn_string}" "${_adb_chk_sql}" "_adb_check" + run_sql "${_adb_chk_sql}" "_adb_check" _rc=$? if (( _rc == 0 )); then @@ -253,8 +256,7 @@ function check_adb() { } function create_adb_user() { - local -r _conn_string="${1}" - local -r _pool_name="${2}" + local -r _pool_name="${1}" local _config_user _config_user=$($ords_cfg_cmd get db.username | tail -1) @@ -320,7 +322,7 @@ function create_adb_user() { /" local _adb_user_sql_output - run_sql "${_conn_string}" "${_adb_user_sql}" "_adb_user_sql_output" + run_sql "${_adb_user_sql}" "_adb_user_sql_output" _rc=$? echo "Installation Output: ${_adb_user_sql_output}" @@ -463,15 +465,14 @@ ords_upgrade() { #------------------------------------------------------------------------------ function get_apex_version() { - local -r _conn_string="${1}" - local -n _db_apex_version="${2}" + local -n _db_apex_version="${1}" local -i _rc=0 sub "APEX version check" local -r _ver_sql="SELECT VERSION FROM DBA_REGISTRY WHERE COMP_ID='APEX';" #local -r _ver_sql="SELECT SCHEMA FROM DBA_REGISTRY WHERE COMP_ID='APEX';" - run_sql "${_conn_string}" "${_ver_sql}" "_db_apex_version" + run_sql "${_ver_sql}" "_db_apex_version" _rc=$? if (( _rc > 0 )); then @@ -608,8 +609,7 @@ function apex_download(){ #------------------------------------------------------------------------------ function apex_upgrade() { - local -r _conn_string="${1}" - local -r _upgrade_key="${2}" + local -r _upgrade_key="${1}" local -i _rc=0 sub "APEX Installation/Upgrade" @@ -630,7 +630,7 @@ function apex_upgrade() { cd "${APEX_INSTALL}" || return 1 SEC=${config["dbpassword"]} local -r _install_sql="@apxsilentins.sql SYSAUX SYSAUX TEMP /i/ $SEC $SEC $SEC $SEC" - run_sql "${_conn_string}" "${_install_sql}" "_install_output" + run_sql "${_install_sql}" "_install_output" _rc=$? echo "Installation Output: ${_install_output:?}" fi @@ -642,7 +642,7 @@ function apex_upgrade() { function apex_housekeeping(){ # check database APEX version regardless of APEX parameters - get_apex_version "${admin_conn_string}" "db_apex_version" + get_apex_version "db_apex_version" if [[ -z "${db_apex_version}" ]]; then echo "FATAL: Unable to get APEX Version for pool \"${pool_name}\"" return 1 @@ -655,7 +655,7 @@ function apex_housekeeping(){ fi # get suggested action - get_apex_action "${admin_conn_string}" "${db_apex_version}" "db_apex_action" + get_apex_action "${config[adminconnect]}" "${db_apex_version}" "db_apex_action" if [[ -z "${db_apex_action}" ]]; then echo "FATAL: Unable to get APEX suggested action for pool \"${pool_name}\"" return 1 @@ -665,7 +665,7 @@ function apex_housekeeping(){ echo "APEX version : \"${db_apex_version}\"" echo "APEX suggested action : $db_apex_action" if [[ ${db_apex_action} != "none" ]]; then - apex_upgrade "${admin_conn_string}" "${pool_name_underscore}_autoupgrade_apex" + apex_upgrade "${config[cloudconfig]}" "${config[adminconnect]}" "${pool_name_underscore}_autoupgrade_apex" rc=$? if (( rc > 0 )); then echo "FATAL: Unable to ${db_apex_action} APEX for pool \"${pool_name}\"" @@ -711,27 +711,27 @@ pool_admin_setup(){ config[dbadminuserpassword]=$(java -cp ${PKILIB} ${PKICLASS} -wrl ${pool_ords_wallet_path} -viewEntry ${PKIENTRY}|grep ${PKIENTRY}|cut -f2 -d\=|cut -f2 -d\ ) fi - prepare_pool_connect_string "admin_conn_string" "${config[dbadminuser]}" "${config[dbadminuserpassword]}" - if [[ -z "${admin_conn_string}" ]]; then + prepare_pool_connect_string config[connect] "${config[dbconnectiontype]}" "${config[dbadminuser]}" "${config[dbadminuserpassword]}" + if [[ -z "${config[connect]}" ]]; then echo "Unable to get admin database connect string for pool \"${pool_name}\"" dump_stack pool_exit[${pool_name}]=1 - continue + return 1 fi is_adb=false - check_adb "${admin_conn_string}" "is_adb" + check_adb "is_adb" rc=$? if (( rc > 0 )); then pool_exit[${pool_name}]=1 - continue + return 1 fi if (( is_adb )); then # Create ORDS User echo "Processing ADB in Pool: ${pool_name}" - create_adb_user "${admin_conn_string}" "${pool_name}" - continue + create_adb_user "${pool_name}" + return 0 fi # not ADB @@ -743,7 +743,7 @@ pool_admin_setup(){ echo "FATAL: unable to manage APEX configuration for pool \"${pool_name}\"" dump_stack pool_exit[${pool_name}]=1 - continue + return 1 fi # database ORDS @@ -754,6 +754,7 @@ pool_admin_setup(){ echo "FATAL: Unable to perform requested ORDS install/upgrade on pool \"${pool_name}\"" pool_exit[${pool_name}]=1 dump_stack + return 1 fi else echo "ORDS Install/Upgrade not requested for pool \"${pool_name}\"" @@ -777,29 +778,19 @@ pool_check(){ if [[ (-z ${config[dbpassword]} ) ]] then - echo "ERROR: password not set, aborting sql check" - pool_exit[${pool_name}]=1 - continue + echo "WARNING: dbpassword not set" fi - prepare_pool_connect_string "ords_conn_string" "${config[dbusername]}" "${config[dbpassword]}" - if [[ -z ${ords_conn_string} ]]; then + prepare_pool_connect_string config[connect] "${config[dbconnectiontype]}" "${config[dbusername]}" "${config[dbpassword]}" + if [[ -z ${config[connect]} ]]; then echo "Unable to get database connect string for pool \"${pool_name}\"" - dump_stack - pool_exit[${pool_name}]=1 - continue + return 1 fi - if [[ (-n ${ords_conn_string}) || (-n ${_cloudconfig}) ]] - then - # DEBUG - # echo ${ords_conn_string} - sql /nolog 2>&1 <<-SQL + local -r _sqlcheck=" set lines 1000 pages 100 feed off WHENEVER SQLERROR EXIT SQL.ERROR WHENEVER OSERROR EXIT 1 - ${_cloudconfig} - connect ${ords_conn_string} alter session set nls_date_format='dd/mm/yyyy hh24:mi:ss'; SELECT SYSDATE, @@ -809,16 +800,23 @@ pool_check(){ SYS_CONTEXT('USERENV','CON_NAME') AS current_pdb FROM dual; prompt - SQL - rc=$? - echo - if (( rc > 0 )); then - echo "ERROR: SQL Test FAILED, sql exit code ${rc}" - exit $rc - fi - else - echo "" - fi + select banner from v\$version; + " + echo "Checking sql connection for pool ${pool_name}" + run_sql "${_sqlcheck}" _checkoutput on + _rc=$? + + echo "--------------------------------------------------------------------------------------------------" + echo "${_checkoutput}" + echo + echo "--------------------------------------------------------------------------------------------------" + + if (( _rc > 0 )); then + echo "SQL check FAILED" + return $_rc + fi + + echo "SQL check SUCCESS" } #------------------------------------------------------------------------------ @@ -841,6 +839,7 @@ check_apex_installation_version if [[ !( -d "${ORDS_CONFIG}/databases/" ) ]] then echo "No database pools found under ${ORDS_CONFIG}/databases" + echo "POOLERRORS 0 POOLS 0" echo "init end" exit 0 fi @@ -874,10 +873,10 @@ for pool in "${ORDS_CONFIG}/databases/"*; do fi sub "Pool Admin setup" - echo "pool : ${pool_name}" - echo "dbadminuser : ${config[dbadminuser]}" - echo "dbconnectiontype: ${config[dbconnectiontype]}" - if [[ (-n "${config[dbadminuser]}") && (-n "${config[dbconnectiontype]}") ]] + echo "pool : ${pool_name}" + echo "dbadminuser : ${config[dbadminuser]}" + echo "dbconnectiontype : ${config[dbconnectiontype]}" + if [[ (-n "${config[dbadminuser]}") ]] then pool_admin_setup rc=$? @@ -886,42 +885,44 @@ for pool in "${ORDS_CONFIG}/databases/"*; do continue fi else - echo "Admin user or connection type not set, skipping admin setup" + echo "Admin user not set, skipping admin setup" fi + # reset conncetion string after admin operations + config[connect]="" + sub "Pool Connection check" - echo "pool : ${pool_name}" - echo "dbusername : ${config[dbusername]}" - echo "dbconnectiontype: ${config[dbconnectiontype]}" - if [[ (-n "${config[dbusername]}") && (-n "${config[dbconnectiontype]}") ]] - then - pool_check - rc=$? - if (( rc > 0 )); then - pool_exit[${pool_name}]=1 - continue - fi - else - echo "Database user or connection type not set, skipping connection check" + echo "pool : ${pool_name}" + echo "dbusername : ${config[dbusername]}" + echo "dbconnectiontype : ${config[dbconnectiontype]}" + echo "cloudconfig : ${config[cloudconfig]}" + pool_check + rc=$? + if (( rc > 0 )); then + pool_exit[${pool_name}]=1 + continue fi - - done sep sub "Exit codes" rc=1 +pools=0 +poolerrors=0 for key in "${!pool_exit[@]}"; do printf "Pool: %-16s Exit Code: %s\n" "${key}" "${pool_exit[${key}]}" + + pools=$(( pools + 1 )) + + # if at least one pool is ok, the return code is 0 if (( ${pool_exit[$key]} == 0 )); then rc=0 + else + poolerrors=$(( poolerrors + 1 )) fi done -# DEBUG -#echo Sleeping forever -#tail -f /dev/null - +echo POOLERRORS $poolerrors POOLS $pools echo "init end" exit $rc