11//
2- // Copyright (c) 2019-2025 Red Hat, Inc.
2+ // Copyright (c) 2019-2026 Red Hat, Inc.
33// Licensed under the Apache License, Version 2.0 (the "License");
44// you may not use this file except in compliance with the License.
55// You may obtain a copy of the License at
@@ -24,6 +24,7 @@ import (
2424
2525 "github.com/devfile/devworkspace-operator/pkg/constants"
2626 "github.com/devfile/devworkspace-operator/pkg/dwerrors"
27+ appsv1 "k8s.io/api/apps/v1"
2728 k8sclient "sigs.k8s.io/controller-runtime/pkg/client"
2829
2930 "github.com/devfile/devworkspace-operator/apis/controller/v1alpha1"
@@ -47,9 +48,10 @@ func ProvisionAutoMountResourcesInto(
4748 api sync.ClusterAPI ,
4849 namespace string ,
4950 persistentHome bool ,
50- isWorkspaceStarted bool ,
51+ isWorkspaceRunning bool ,
52+ workspaceDeployment * appsv1.Deployment ,
5153) error {
52- resources , err := getAutomountResources (api , namespace , isWorkspaceStarted )
54+ resources , err := getAutomountResources (api , namespace , isWorkspaceRunning , workspaceDeployment )
5355
5456 if err != nil {
5557 return err
@@ -82,18 +84,23 @@ func ProvisionAutoMountResourcesInto(
8284 return nil
8385}
8486
85- func getAutomountResources (api sync.ClusterAPI , namespace string , isWorkspaceStarted bool ) (* Resources , error ) {
86- gitCMAutoMountResources , err := ProvisionGitConfiguration (api , namespace , isWorkspaceStarted )
87+ func getAutomountResources (
88+ api sync.ClusterAPI ,
89+ namespace string ,
90+ isWorkspaceRunning bool ,
91+ workspaceDeployment * appsv1.Deployment ,
92+ ) (* Resources , error ) {
93+ gitCMAutoMountResources , err := ProvisionGitConfiguration (api , namespace , isWorkspaceRunning , workspaceDeployment )
8794 if err != nil {
8895 return nil , err
8996 }
9097
91- cmAutoMountResources , err := getDevWorkspaceConfigmaps (namespace , api , isWorkspaceStarted )
98+ cmAutoMountResources , err := getDevWorkspaceConfigmaps (namespace , api , isWorkspaceRunning , workspaceDeployment )
9299 if err != nil {
93100 return nil , err
94101 }
95102
96- secretAutoMountResources , err := getDevWorkspaceSecrets (namespace , api , isWorkspaceStarted )
103+ secretAutoMountResources , err := getDevWorkspaceSecrets (namespace , api , isWorkspaceRunning , workspaceDeployment )
97104 if err != nil {
98105 return nil , err
99106 }
@@ -110,7 +117,7 @@ func getAutomountResources(api sync.ClusterAPI, namespace string, isWorkspaceSta
110117 }
111118 dropItemsFieldFromVolumes (mergedResources .Volumes )
112119
113- pvcAutoMountResources , err := getAutoMountPVCs (namespace , api , isWorkspaceStarted )
120+ pvcAutoMountResources , err := getAutoMountPVCs (namespace , api , isWorkspaceRunning , workspaceDeployment )
114121 if err != nil {
115122 return nil , err
116123 }
@@ -360,3 +367,81 @@ func sortConfigmaps(cms []corev1.ConfigMap) {
360367 return cms [i ].Name < cms [j ].Name
361368 })
362369}
370+
371+ func isMountOnStart (obj k8sclient.Object ) bool {
372+ return obj .GetAnnotations ()[constants .MountOnStartAttribute ] == "true"
373+ }
374+
375+ // isAllowedToMount checks whether an automount resource can be added to the workspace pod.
376+ // Resources marked with mount-on-start are only allowed when
377+ // the workspace is not yet running or when they are already present in the current deployment.
378+ func isAllowedToMount (
379+ obj k8sclient.Object ,
380+ automountResource Resources ,
381+ isWorkspaceRunning bool ,
382+ workspaceDeployment * appsv1.Deployment ,
383+ ) bool {
384+ // No existing deployment to compare against — allow everything
385+ if workspaceDeployment == nil {
386+ return true
387+ }
388+
389+ // Resource without mount-on-start is always eligible
390+ if ! isMountOnStart (obj ) {
391+ return true
392+ }
393+
394+ // Workspace is not yet running — the pod will be (re)created with these resources included
395+ if ! isWorkspaceRunning {
396+ return true
397+ }
398+
399+ // Workspace is already running — only allow if already present in the deployment
400+ return existsInDeployment (automountResource , workspaceDeployment )
401+ }
402+
403+ func existsInDeployment (automountResource Resources , workspaceDeployment * appsv1.Deployment ) bool {
404+ return isVolumeMountExistsInDeployment (automountResource , workspaceDeployment ) ||
405+ isEnvFromSourceExistsInDeployment (automountResource , workspaceDeployment )
406+ }
407+
408+ // isVolumeMountExistsInDeployment returns true if any volume from the automount resource
409+ // is already present in the workspace deployment's pod spec. Comparison is by name only,
410+ // ignoring VolumeSource — if a name is reused after deleting the old resource, the deletion
411+ // triggers reconciliation and a workspace restart before the new resource is mounted.
412+ func isVolumeMountExistsInDeployment (automountResource Resources , workspaceDeployment * appsv1.Deployment ) bool {
413+ for _ , automountVolumes := range automountResource .Volumes {
414+ for _ , deploymentVolumes := range workspaceDeployment .Spec .Template .Spec .Volumes {
415+ if automountVolumes .Name == deploymentVolumes .Name {
416+ return true
417+ }
418+ }
419+ }
420+
421+ return false
422+ }
423+
424+ // isEnvFromSourceExistsInDeployment returns true if any EnvFromSource from the automount resource
425+ // is already referenced in a container of the workspace deployment, matched by ConfigMap or Secret name.
426+ func isEnvFromSourceExistsInDeployment (automountResource Resources , workspaceDeployment * appsv1.Deployment ) bool {
427+ for _ , container := range workspaceDeployment .Spec .Template .Spec .Containers {
428+
429+ for _ , automountEnvFrom := range automountResource .EnvFromSource {
430+ for _ , containerEnvFrom := range container .EnvFrom {
431+ if automountEnvFrom .ConfigMapRef != nil && containerEnvFrom .ConfigMapRef != nil &&
432+ automountEnvFrom .ConfigMapRef .Name == containerEnvFrom .ConfigMapRef .Name {
433+
434+ return true
435+ }
436+
437+ if automountEnvFrom .SecretRef != nil && containerEnvFrom .SecretRef != nil &&
438+ automountEnvFrom .SecretRef .Name == containerEnvFrom .SecretRef .Name {
439+
440+ return true
441+ }
442+ }
443+ }
444+ }
445+
446+ return false
447+ }
0 commit comments