diff --git a/components/odh-notebook-controller/controllers/notebook_controller.go b/components/odh-notebook-controller/controllers/notebook_controller.go index 872e7a77b79..a3be9d42ce7 100644 --- a/components/odh-notebook-controller/controllers/notebook_controller.go +++ b/components/odh-notebook-controller/controllers/notebook_controller.go @@ -16,7 +16,10 @@ limitations under the License. package controllers import ( + "bytes" "context" + "crypto/x509" + "encoding/pem" "errors" "reflect" "strconv" @@ -37,6 +40,8 @@ import ( "k8s.io/client-go/util/retry" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/handler" + "sigs.k8s.io/controller-runtime/pkg/reconcile" ) const ( @@ -140,6 +145,27 @@ func (r *OpenshiftNotebookReconciler) Reconcile(ctx context.Context, req ctrl.Re return ctrl.Result{}, err } + // Create Configmap with the ODH notebook certificate + // With the ODH 2.8 Operator, user can provide their own certificate + // from DSCI initializer, that provides the certs in a ConfigMap odh-trusted-ca-bundle + // create a separate ConfigMap for the notebook which append the user provided certs + // with cluster self-signed certs. + err = r.CreateNotebookCertConfigMap(notebook, ctx) + if err != nil { + return ctrl.Result{}, err + } else { + // If createNotebookCertConfigMap returns nil, + // and still the ConfigMap workbench-trusted-ca-bundle is not found, + // reconcile notebook to unset the env variable. + if r.IsConfigMapDeleted(notebook, ctx) { + // Unset the env variable in the notebook + err = r.UnsetNotebookCertConfig(notebook, ctx) + if err != nil { + return ctrl.Result{}, err + } + } + } + // Call the Network Policies reconciler err = r.ReconcileAllNetworkPolicies(notebook, ctx) if err != nil { @@ -192,6 +218,211 @@ func (r *OpenshiftNotebookReconciler) Reconcile(ctx context.Context, req ctrl.Re return ctrl.Result{}, nil } +// createNotebookCertConfigMap creates a ConfigMap workbench-trusted-ca-bundle +// that contains the root certificates from the ConfigMap odh-trusted-ca-bundle +// and the self-signed certificates from the ConfigMap kube-root-ca.crt +// The ConfigMap workbench-trusted-ca-bundle is used by the notebook to trust +// the root and self-signed certificates. +func (r *OpenshiftNotebookReconciler) CreateNotebookCertConfigMap(notebook *nbv1.Notebook, + ctx context.Context) error { + + // Initialize logger format + log := r.Log.WithValues("notebook", notebook.Name, "namespace", notebook.Namespace) + + rootCertPool := [][]byte{} // Root certificate pool + odhConfigMapName := "odh-trusted-ca-bundle" // Use ODH Trusted CA Bundle Contains ca-bundle.crt and odh-ca-bundle.crt + selfSignedConfigMapName := "kube-root-ca.crt" // Self-Signed Certs Contains ca.crt + + configMapList := []string{odhConfigMapName, selfSignedConfigMapName} + configMapFileNames := map[string][]string{ + odhConfigMapName: {"ca-bundle.crt", "odh-ca-bundle.crt"}, + selfSignedConfigMapName: {"ca.crt"}, + } + + for _, configMapName := range configMapList { + + configMap := &corev1.ConfigMap{} + if err := r.Get(ctx, client.ObjectKey{Namespace: notebook.Namespace, Name: configMapName}, configMap); err != nil { + // if configmap odh-trusted-ca-bundle is not found, + // no need to create the workbench-trusted-ca-bundle + if apierrs.IsNotFound(err) && configMapName == odhConfigMapName { + return nil + } + log.Info("Unable to fetch ConfigMap", "configMap", configMapName) + continue + } + + // Search for the certificate in the ConfigMap + for _, certFile := range configMapFileNames[configMapName] { + + certData, ok := configMap.Data[certFile] + // If ca-bundle.crt is not found in the ConfigMap odh-trusted-ca-bundle + // no need to create the workbench-trusted-ca-bundle, as it is created + // by annotation inject-ca-bundle: "true" + if !ok || certFile == "ca-bundle.crt" && certData == "" { + return nil + } + if !ok || certData == "" { + continue + } + + // Attempt to decode PEM encoded certificate + block, _ := pem.Decode([]byte(certData)) + if block != nil && block.Type == "CERTIFICATE" { + // Attempt to parse the certificate + _, err := x509.ParseCertificate(block.Bytes) + if err != nil { + log.Error(err, "Error parsing certificate", "configMap", configMap.Name, "certFile", certFile) + continue + } + // Add the certificate to the pool + rootCertPool = append(rootCertPool, []byte(certData)) + } else if len(certData) > 0 { + log.Info("Invalid certificate format", "configMap", configMap.Name, "certFile", certFile) + } + } + } + + if len(rootCertPool) > 0 { + desiredTrustedCAConfigMap := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "workbench-trusted-ca-bundle", + Namespace: notebook.Namespace, + Labels: map[string]string{"opendatahub.io/managed-by": "workbenches"}, + }, + Data: map[string]string{ + "ca-bundle.crt": string(bytes.Join(rootCertPool, []byte{})), + }, + } + + foundTrustedCAConfigMap := &corev1.ConfigMap{} + err := r.Get(ctx, client.ObjectKey{ + Namespace: desiredTrustedCAConfigMap.Namespace, + Name: desiredTrustedCAConfigMap.Name, + }, foundTrustedCAConfigMap) + if err != nil { + if apierrs.IsNotFound(err) { + r.Log.Info("Creating workbench-trusted-ca-bundle configmap", "namespace", notebook.Namespace, "notebook", notebook.Name) + err = r.Create(ctx, desiredTrustedCAConfigMap) + if err != nil && !apierrs.IsAlreadyExists(err) { + r.Log.Error(err, "Unable to create the workbench-trusted-ca-bundle ConfigMap") + return err + } else { + r.Log.Info("Created workbench-trusted-ca-bundle ConfigMap", "namespace", notebook.Namespace, "notebook", notebook.Name) + } + } + } else if err == nil && !reflect.DeepEqual(foundTrustedCAConfigMap.Data, desiredTrustedCAConfigMap.Data) { + // some data has changed, update the ConfigMap + r.Log.Info("Updating workbench-trusted-ca-bundle ConfigMap", "namespace", notebook.Namespace, "notebook", notebook.Name) + foundTrustedCAConfigMap.Data = desiredTrustedCAConfigMap.Data + err = r.Update(ctx, foundTrustedCAConfigMap) + if err != nil { + r.Log.Error(err, "Unable to update the workbench-trusted-ca-bundle ConfigMap") + return err + } + } + } + return nil +} + +// IsConfigMapDeleted check if configmap is deleted +// and the notebook is using the configmap as a volume +func (r *OpenshiftNotebookReconciler) IsConfigMapDeleted(notebook *nbv1.Notebook, ctx context.Context) bool { + + // Initialize logger format + log := r.Log.WithValues("notebook", notebook.Name, "namespace", notebook.Namespace) + + var workbenchConfigMapExists bool + workbenchConfigMapExists = false + + foundTrustedCAConfigMap := &corev1.ConfigMap{} + err := r.Get(ctx, client.ObjectKey{ + Namespace: notebook.Namespace, + Name: "workbench-trusted-ca-bundle", + }, foundTrustedCAConfigMap) + if err == nil { + workbenchConfigMapExists = true + } + + if !workbenchConfigMapExists { + for _, volume := range notebook.Spec.Template.Spec.Volumes { + if volume.ConfigMap != nil && volume.ConfigMap.Name == "workbench-trusted-ca-bundle" { + log.Info("workbench-trusted-ca-bundle ConfigMap is deleted and used by the notebook as a volume") + return true + } + } + } + return false +} + +// UnsetEnvVars removes the environment variables from the notebook container +func (r *OpenshiftNotebookReconciler) UnsetNotebookCertConfig(notebook *nbv1.Notebook, ctx context.Context) error { + + // Initialize logger format + log := r.Log.WithValues("notebook", notebook.Name, "namespace", notebook.Namespace) + + // Get the notebook object + envVars := []string{"PIP_CERT", "REQUESTS_CA_BUNDLE", "SSL_CERT_FILE", "PIPELINES_SSL_SA_CERTS"} + notebookSpecChanged := false + patch := client.MergeFrom(notebook.DeepCopy()) + copyNotebook := notebook.DeepCopy() + + notebookContainers := ©Notebook.Spec.Template.Spec.Containers + notebookVolumes := ©Notebook.Spec.Template.Spec.Volumes + var imgContainer corev1.Container + + // Unset the env variables in the notebook + for _, container := range *notebookContainers { + // Update notebook image container with env Variables + if container.Name == notebook.Name { + imgContainer = container + for _, key := range envVars { + for index, env := range imgContainer.Env { + if key == env.Name { + imgContainer.Env = append(imgContainer.Env[:index], imgContainer.Env[index+1:]...) + } + } + } + // Unset VolumeMounts in the notebook + for index, volumeMount := range imgContainer.VolumeMounts { + if volumeMount.Name == "trusted-ca" { + imgContainer.VolumeMounts = append(imgContainer.VolumeMounts[:index], imgContainer.VolumeMounts[index+1:]...) + } + } + // Update container with Env and Volume Mount Changes + for index, container := range *notebookContainers { + if container.Name == notebook.Name { + (*notebookContainers)[index] = imgContainer + notebookSpecChanged = true + break + } + } + break + } + } + + // Unset Volume in the notebook + for index, volume := range *notebookVolumes { + if volume.ConfigMap != nil && volume.ConfigMap.Name == "workbench-trusted-ca-bundle" { + *notebookVolumes = append((*notebookVolumes)[:index], (*notebookVolumes)[index+1:]...) + notebookSpecChanged = true + break + } + } + + if notebookSpecChanged { + // Update the notebook with the new container + err := r.Patch(ctx, copyNotebook, patch) + if err != nil { + log.Error(err, "Unable to update the notebook for removing the env variables") + return err + } + log.Info("Removed the env variables from the notebook", "notebook", notebook.Name, "namespace", notebook.Namespace) + } + + return nil +} + // SetupWithManager sets up the controller with the Manager. func (r *OpenshiftNotebookReconciler) SetupWithManager(mgr ctrl.Manager) error { builder := ctrl.NewControllerManagedBy(mgr). @@ -200,8 +431,70 @@ func (r *OpenshiftNotebookReconciler) SetupWithManager(mgr ctrl.Manager) error { Owns(&corev1.ServiceAccount{}). Owns(&corev1.Service{}). Owns(&corev1.Secret{}). - Owns(&netv1.NetworkPolicy{}) + Owns(&netv1.NetworkPolicy{}). + + // Watch for all the required ConfigMaps + // odh-trusted-ca-bundle, kube-root-ca.crt, workbench-trusted-ca-bundle + // and reconcile the workbench-trusted-ca-bundle ConfigMap, + Watches(&corev1.ConfigMap{}, + handler.EnqueueRequestsFromMapFunc(func(ctx context.Context, o client.Object) []reconcile.Request { + log := r.Log.WithValues("namespace", o.GetNamespace(), "name", o.GetName()) + + // If the ConfigMap is odh-trusted-ca-bundle or kube-root-ca.crt + // trigger a reconcile event for first notebook in the namespace + if o.GetName() == "odh-trusted-ca-bundle" { + // List all the notebooks in the namespace and trigger a reconcile event + var nbList nbv1.NotebookList + if err := r.List(ctx, &nbList, client.InNamespace(o.GetNamespace())); err != nil { + log.Error(err, "Unable to list Notebooks when attempting to handle Global CA Bundle event.") + return []reconcile.Request{} + } + + // As there is only one configmap workbench-trusted-ca-bundle per namespace + // and is used by all the notebooks in the namespace, we can trigger + // reconcile event only for the first notebook in the list. + for _, nb := range nbList.Items { + return []reconcile.Request{ + { + NamespacedName: types.NamespacedName{ + Name: nb.Name, + Namespace: o.GetNamespace(), + }, + }, + } + } + } + + // If the ConfigMap is workbench-trusted-ca-bundle + // trigger a reconcile event for all the notebooks in the namespace + // containing the ConfigMap workbench-trusted-ca-bundle as a volume. + if o.GetName() == "workbench-trusted-ca-bundle" { + // List all the notebooks in the namespace and trigger a reconcile event + var nbList nbv1.NotebookList + if err := r.List(ctx, &nbList, client.InNamespace(o.GetNamespace())); err != nil { + log.Error(err, "Unable to list Notebook's when attempting to handle Global CA Bundle event.") + return []reconcile.Request{} + } + // For all the notebooks that mounted the ConfigMap workbench-trusted-ca-bundle + // as a volume, trigger a reconcile event. + reconcileRequests := []reconcile.Request{} + for _, nb := range nbList.Items { + for _, volume := range nb.Spec.Template.Spec.Volumes { + if volume.ConfigMap != nil && volume.ConfigMap.Name == o.GetName() { + namespacedName := types.NamespacedName{ + Name: nb.Name, + Namespace: o.GetNamespace(), + } + reconcileRequests = append(reconcileRequests, reconcile.Request{NamespacedName: namespacedName}) + } + } + } + return reconcileRequests + } + return []reconcile.Request{} + }), + ) err := builder.Complete(r) if err != nil { return err diff --git a/components/odh-notebook-controller/controllers/notebook_controller_test.go b/components/odh-notebook-controller/controllers/notebook_controller_test.go index b3f359ddc3c..4958a719d42 100644 --- a/components/odh-notebook-controller/controllers/notebook_controller_test.go +++ b/components/odh-notebook-controller/controllers/notebook_controller_test.go @@ -213,11 +213,11 @@ var _ = Describe("The Openshift Notebook controller", func() { } // Assert that the volume mount and volume are added correctly - volumeMountPath := "/etc/pki/tls/certs/custom-ca-bundle.crt" + volumeMountPath := "/etc/pki/tls/custom-certs/ca-bundle.crt" expectedVolumeMount := corev1.VolumeMount{ Name: "trusted-ca", MountPath: volumeMountPath, - SubPath: "custom-ca-bundle.crt", + SubPath: "ca-bundle.crt", ReadOnly: true, } if len(notebook.Spec.Template.Spec.Containers[0].VolumeMounts) == 0 { @@ -237,11 +237,7 @@ var _ = Describe("The Openshift Notebook controller", func() { Items: []corev1.KeyToPath{ { Key: "ca-bundle.crt", - Path: "custom-ca-bundle.crt", - }, - { - Key: "odh-ca-bundle.crt", - Path: "custom-odh-ca-bundle.crt", + Path: "ca-bundle.crt", }, }, }, diff --git a/components/odh-notebook-controller/controllers/notebook_webhook.go b/components/odh-notebook-controller/controllers/notebook_webhook.go index 794fcf501c8..004f1307f72 100644 --- a/components/odh-notebook-controller/controllers/notebook_webhook.go +++ b/components/odh-notebook-controller/controllers/notebook_webhook.go @@ -17,9 +17,7 @@ package controllers import ( "context" - "crypto/x509" "encoding/json" - "encoding/pem" "fmt" "net/http" @@ -36,6 +34,10 @@ import ( "sigs.k8s.io/controller-runtime/pkg/webhook/admission" ) +const ( + WorkbenchLabel = "opendatahub.io/workbenches" +) + //+kubebuilder:webhook:path=/mutate-notebook-v1,mutating=true,failurePolicy=fail,sideEffects=None,groups=kubeflow.org,resources=notebooks,verbs=create;update,versions=v1,name=notebooks.opendatahub.io,admissionReviewVersions=v1 // NotebookWebhook holds the webhook configuration. @@ -236,18 +238,26 @@ func (w *NotebookWebhook) Handle(ctx context.Context, req admission.Request) adm return admission.Errored(http.StatusBadRequest, err) } - // Inject the the reconciliation lock only on new notebook creation + // Inject the reconciliation lock only on new notebook creation if req.Operation == admissionv1.Create { + AddWorkbenchLabel(notebook) err = InjectReconciliationLock(¬ebook.ObjectMeta) if err != nil { return admission.Errored(http.StatusInternalServerError, err) } + + // Only Mount ca bundle on new notebook creation err = CheckAndMountCACertBundle(ctx, w.Client, notebook, log) if err != nil { return admission.Errored(http.StatusInternalServerError, err) } } + // Add the workbench label on notebook update + if req.Operation == admissionv1.Update { + AddWorkbenchLabel(notebook) + } + // Inject the OAuth proxy if the annotation is present if OAuthInjectionIsEnabled(notebook.ObjectMeta) { err = InjectOAuthProxy(notebook, w.OAuthConfig) @@ -271,122 +281,157 @@ func (w *NotebookWebhook) InjectDecoder(d *admission.Decoder) error { return nil } +// AddWorkbenchLabel adds an exclusive static label to the Notebook pods +func AddWorkbenchLabel(notebook *nbv1.Notebook) { + currentLabels := notebook.ObjectMeta.GetLabels() + notebook.ObjectMeta.Labels[WorkbenchLabel] = "true" + notebook.ObjectMeta.SetLabels(currentLabels) +} + // CheckAndMountCACertBundle checks if the odh-trusted-ca-bundle ConfigMap is present func CheckAndMountCACertBundle(ctx context.Context, cli client.Client, notebook *nbv1.Notebook, log logr.Logger) error { - // Define the name of the ConfigMap to be mounted - configMapName := "odh-trusted-ca-bundle" + workbenchConfigMapName := "workbench-trusted-ca-bundle" + odhConfigMapName := "odh-trusted-ca-bundle" - // get configmap based on its name and the namespace - configMap := &corev1.ConfigMap{} - if err := cli.Get(ctx, client.ObjectKey{Namespace: notebook.Namespace, Name: configMapName}, configMap); err != nil { - log.Info("Unable to fetch ConfigMap", "configMap", configMapName) + // if the odh-trusted-ca-bundle ConfigMap is not present, skip the process + // as operator might have disabled the feature. + odhConfigMap := &corev1.ConfigMap{} + odhErr := cli.Get(ctx, client.ObjectKey{Namespace: notebook.Namespace, Name: odhConfigMapName}, odhConfigMap) + if odhErr != nil { + log.Info("odh-trusted-ca-bundle ConfigMap is not present, not starting mounting process.") return nil } - // Search for the odh-trusted-ca-bundle ConfigMap - cm := configMap - if cm.Name == configMapName { - - volumeName := "trusted-ca" - caVolumeMountPath := "/etc/pki/tls/certs/custom-ca-bundle.crt" - odhVolumeMountPath := "/etc/pki/tls/certs/custom-odh-ca-bundle.crt" - // Define volume mounts for both certificates - volumeMounts := []corev1.VolumeMount{} - - isEmpty, err := certValidator(cm, "ca-bundle.crt", log) - if err == nil { - if isEmpty { - log.Info("Certificates in 'ca-bundle.crt' are empty, skipping mounting") - } else { - customCAVolumeMounts := []corev1.VolumeMount{ - { - Name: volumeName, - MountPath: caVolumeMountPath, - SubPath: "custom-ca-bundle.crt", - ReadOnly: true, - }, - } - volumeMounts = append(volumeMounts, customCAVolumeMounts...) - log.Info("Certificates in 'ca-bundle.crt' are valid and can be mounted") - } - } else { - log.Error(err, "Error validating certificates for ca-bundle.crt. They cannot be mounted") - } + // if the workbench-trusted-ca-bundle ConfigMap is not present, + // controller was not successful in creating the ConfigMap, skip the process + workbenchConfigMap := &corev1.ConfigMap{} + err := cli.Get(ctx, client.ObjectKey{Namespace: notebook.Namespace, Name: workbenchConfigMapName}, workbenchConfigMap) + if err != nil { + log.Info("workbench-trusted-ca-bundle ConfigMap is not present, skipping mounting of certificates.") + return nil + } - isEmpty, err = certValidator(cm, "odh-ca-bundle.crt", log) - if err == nil { - if isEmpty { - log.Info("Certificates in 'odh-ca-bundle.crt' are empty, skipping mounting") - } else { - customODHCAVolumeMounts := []corev1.VolumeMount{ - { - Name: volumeName, - MountPath: odhVolumeMountPath, - SubPath: "custom-odh-ca-bundle.crt", - ReadOnly: true, - }, - } - volumeMounts = append(volumeMounts, customODHCAVolumeMounts...) - log.Info("Certificates in 'odh-ca-bundle.crt' are valid and can be mounted") - } - } else { - log.Error(err, "Error validating certificates for odh-ca-bundle.crt. They cannot be mounted") - } + cm := workbenchConfigMap + if cm.Name == workbenchConfigMapName { + // Inject the trusted-ca volume and environment variables + log.Info("Injecting trusted-ca volume and environment variables", notebook.Name, "namespace", notebook.Namespace) + return InjectCertConfig(notebook, workbenchConfigMapName) + } + return nil +} + +func InjectCertConfig(notebook *nbv1.Notebook, configMapName string) error { + + // ConfigMap details + configVolumeName := "trusted-ca" + configMapMountPath := "/etc/pki/tls/custom-certs/ca-bundle.crt" + configMapMountKey := "ca-bundle.crt" + configMapMountValue := "ca-bundle.crt" + configEnvVars := map[string]string{ + "PIP_CERT": configMapMountPath, + "REQUESTS_CA_BUNDLE": configMapMountPath, + "SSL_CERT_FILE": configMapMountPath, + "PIPELINES_SSL_SA_CERTS": configMapMountPath, + } - // Add volume mount to the pod's spec - notebook.Spec.Template.Spec.Containers[0].VolumeMounts = append(notebook.Spec.Template.Spec.Containers[0].VolumeMounts, volumeMounts...) - - // Create volume for mounting the CA certificate from the ConfigMap with key and path - configMapVolume := corev1.Volume{ - Name: volumeName, - VolumeSource: corev1.VolumeSource{ - ConfigMap: &corev1.ConfigMapVolumeSource{ - LocalObjectReference: corev1.LocalObjectReference{Name: cm.Name}, - Optional: pointer.Bool(true), - Items: []corev1.KeyToPath{ - { - Key: "ca-bundle.crt", - Path: "custom-ca-bundle.crt", - }, - { - Key: "odh-ca-bundle.crt", - Path: "custom-odh-ca-bundle.crt", - }, - }, + notebookContainers := ¬ebook.Spec.Template.Spec.Containers + var imgContainer corev1.Container + + // Add trusted-ca volume + notebookVolumes := ¬ebook.Spec.Template.Spec.Volumes + certVolumeExists := false + certVolume := corev1.Volume{ + Name: configVolumeName, + VolumeSource: corev1.VolumeSource{ + ConfigMap: &corev1.ConfigMapVolumeSource{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: configMapName, + }, + Optional: pointer.Bool(true), + Items: []corev1.KeyToPath{{ + Key: configMapMountKey, + Path: configMapMountValue, + }, }, }, + }, + } + for index, volume := range *notebookVolumes { + if volume.Name == configVolumeName { + (*notebookVolumes)[index] = certVolume + certVolumeExists = true + break } - - // Add volume to the pod's spec - notebook.Spec.Template.Spec.Volumes = append(notebook.Spec.Template.Spec.Volumes, configMapVolume) - return nil } - return nil -} + if !certVolumeExists { + *notebookVolumes = append(*notebookVolumes, certVolume) + } -func certValidator(cm *corev1.ConfigMap, dataKey string, log logr.Logger) (bool, error) { + // Update Notebook Image container with env variables and Volume Mounts + for _, container := range *notebookContainers { + // Update notebook image container with env Variables + if container.Name == notebook.Name { + var newVars []corev1.EnvVar + imgContainer = container + + for key, val := range configEnvVars { + keyExists := false + for _, env := range imgContainer.Env { + if key == env.Name { + keyExists = true + // Update if env value is updated + if env.Value != val { + env.Value = val + } + } + } + if !keyExists { + newVars = append(newVars, corev1.EnvVar{Name: key, Value: val}) + } + } - odhCertData, ok := cm.Data[dataKey] - if !ok || odhCertData == "" { - // Print a warning if odh-ca-bundle.crt data is empty - log.Info(fmt.Sprintf("%s data are empty", dataKey)) - return true, nil // Indicate that the certificate is empty - } + // Update container only when required env variables are not present + imgContainerExists := false + if len(newVars) != 0 { + imgContainer.Env = append(imgContainer.Env, newVars...) + } - // Attempt to decode PEM encoded certificate - odhBlock, _ := pem.Decode([]byte(odhCertData)) - if odhBlock != nil && odhBlock.Type == "CERTIFICATE" { - // Attempt to parse the certificate - _, err := x509.ParseCertificate(odhBlock.Bytes) - if err != nil { - log.Error(nil, fmt.Sprintf("error parsing certificate for key '%s' in ConfigMap odh-trusted-ca-bundle", dataKey)) - return false, fmt.Errorf("error parsing certificate for key '%s' in ConfigMap odh-trusted-ca-bundle", dataKey) + // Create Volume mount + volumeMountExists := false + containerVolMounts := &imgContainer.VolumeMounts + trustedCAVolMount := corev1.VolumeMount{ + Name: configVolumeName, + ReadOnly: true, + MountPath: configMapMountPath, + SubPath: configMapMountValue, + } + + for index, volumeMount := range *containerVolMounts { + if volumeMount.Name == configVolumeName { + (*containerVolMounts)[index] = trustedCAVolMount + volumeMountExists = true + break + } + } + if !volumeMountExists { + *containerVolMounts = append(*containerVolMounts, trustedCAVolMount) + } + + // Update container with Env and Volume Mount Changes + for index, container := range *notebookContainers { + if container.Name == notebook.Name { + (*notebookContainers)[index] = imgContainer + imgContainerExists = true + break + } + } + + if !imgContainerExists { + return fmt.Errorf("notebook image container not found %v", notebook.Name) + } + break } - } else if len(odhCertData) > 0 { - log.Error(nil, fmt.Sprintf("invalid certificate format for key '%s' in ConfigMap odh-trusted-ca-bundle", dataKey)) - return false, fmt.Errorf("invalid certificate format for key '%s' in ConfigMap odh-trusted-ca-bundle", dataKey) } - - return false, nil + return nil }