Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions docs/additional-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,11 @@ By default, resources will be mounted based on the resource name:
Mounting resources can be additionally configured via **annotations**:
* `controller.devfile.io/mount-path`: configure where the resource should be mounted
* `controller.devfile.io/mount-as`: for secrets and configmaps only, configure how the resource should be mounted to the workspace
* If `controller.devfile.io/mount-as: file`, the configmap/secret will be mounted as files within the mount path. This is the default behavior
* If `controller.devfile.io/mount-as: env`, the keys and values in the configmap/secret will be mounted as environment variables in all containers in the DevWorkspace
* If `controller.devfile.io/mount-as: file`, the configmap/secret will be mounted as files within the mount path. This is the default behavior.
* If `controller.devfile.io/mount-as: subpath`, the keys and values in the configmap/secret will be mounted as files within the mount path using subpath volume mounts.
* If `controller.devfile.io/mount-as: env`, the keys and values in the configmap/secret will be mounted as environment variables in all containers in the DevWorkspace.

When "file" is used, the configmap is mounted as a directory within the workspace, erasing any files/directories already present. When "subpath" is used, each key in the configmap/secret is mounted as a subpath volume mount in the mount path, leaving existing files intact but preventing changes to the secret/configmap from propagating into the workspace without a restart.
* `controller.devfile.io/read-only`: for persistent volume claims, mount the resource as read-only

## Adding image pull secrets to workspaces
Expand Down
9 changes: 7 additions & 2 deletions pkg/constants/metadata.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ const (
// secrets that should be seen by the controller
DevWorkspaceWatchSecretLabel = "controller.devfile.io/watch-secret"

// DevWorkspaceMountLabel is the label key to store if a configmap or secret should be mounted to the devworkspace
// DevWorkspaceMountLabel is the label key to store if a configmap, secret, or PVC should be mounted to the devworkspace
DevWorkspaceMountLabel = "controller.devfile.io/mount-to-devworkspace"

// DevWorkspaceGitCredentialLabel is the label key to specify if the secret is a git credential. All secrets who
Expand All @@ -61,7 +61,12 @@ const (
// DevWorkspaceMountAsAnnotation is the annotation key to configure the way how configmaps or secrets should be mounted.
// Supported options:
// - "env" - mount as environment variables
// - "file" - mount as a file
// - "file" - mount as files within the mount path
// - "subpath" - mount keys as subpath volume mounts within the mount path
// When a configmap or secret is mounted via "file", the keys within the configmap/secret are mounted as files
// within a directory, erasing all contents of the directory. Mounting via "subpath" leaves existing files in the
// mount directory changed, but prevents on-cluster changes to the configmap/secret propagating to the container
// until it is restarted.
// If mountAs is not provided, the default behaviour will be to mount as a file.
DevWorkspaceMountAsAnnotation = "controller.devfile.io/mount-as"

Expand Down
28 changes: 24 additions & 4 deletions pkg/provision/workspace/automount/configmaps.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,18 @@ func getDevWorkspaceConfigmaps(namespace string, api sync.ClusterAPI) (*v1alpha1
mountAs := configmap.Annotations[constants.DevWorkspaceMountAsAnnotation]
if mountAs == "env" {
additionalEnvVars = append(additionalEnvVars, getAutoMountConfigMapEnvFromSource(configmap.Name))
continue
}
mountPath := configmap.Annotations[constants.DevWorkspaceMountPathAnnotation]
if mountPath == "" {
mountPath = path.Join("/etc/config/", configmap.Name)
}
if mountAs == "subpath" {
podAdditions.Volumes = append(podAdditions.Volumes, GetAutoMountVolumeWithConfigMap(configmap.Name))
podAdditions.VolumeMounts = append(podAdditions.VolumeMounts, GetAutoMountConfigMapSubpathVolumeMounts(mountPath, configmap)...)
} else {
mountPath := configmap.Annotations[constants.DevWorkspaceMountPathAnnotation]
if mountPath == "" {
mountPath = path.Join("/etc/config/", configmap.Name)
}
// mountAs == "file", "", or anything else (default). Don't treat invalid values as errors to avoid
// failing all workspace starts in this namespace
podAdditions.Volumes = append(podAdditions.Volumes, GetAutoMountVolumeWithConfigMap(configmap.Name))
podAdditions.VolumeMounts = append(podAdditions.VolumeMounts, GetAutoMountConfigMapVolumeMount(mountPath, configmap.Name))
}
Expand Down Expand Up @@ -77,6 +84,19 @@ func GetAutoMountConfigMapVolumeMount(mountPath, name string) corev1.VolumeMount
return workspaceVolumeMount
}

func GetAutoMountConfigMapSubpathVolumeMounts(mountPath string, cm corev1.ConfigMap) []corev1.VolumeMount {
var workspaceVolumeMounts []corev1.VolumeMount
for configmapKey := range cm.Data {
workspaceVolumeMounts = append(workspaceVolumeMounts, corev1.VolumeMount{
Name: common.AutoMountConfigMapVolumeName(cm.Name),
ReadOnly: true,
MountPath: path.Join(mountPath, configmapKey),
SubPath: configmapKey,
})
}
return workspaceVolumeMounts
}

func getAutoMountConfigMapEnvFromSource(name string) corev1.EnvFromSource {
return corev1.EnvFromSource{
ConfigMapRef: &corev1.ConfigMapEnvSource{
Expand Down
4 changes: 4 additions & 0 deletions pkg/provision/workspace/automount/git-config.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ func provisionGitConfig(api sync.ClusterAPI, namespace string, userMountPath str
return podAdditions, nil
}

if gitconfig == "" {
// Nothing to store in configmap, so don't create one
return nil, nil
}
configMapPodAdditions, err := mountGitConfigMap(gitCredentialsConfigMapName, namespace, api, gitconfig)
if err != nil {
return configMapPodAdditions, err
Expand Down
6 changes: 4 additions & 2 deletions pkg/provision/workspace/automount/git-provisioner.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,10 @@ func provisionGitConfiguration(api sync.ClusterAPI, namespace string) (*v1alpha1
return podAdditions, err
}

podAdditions.Volumes = append(podAdditions.Volumes, gitConfigAdditions.Volumes...)
podAdditions.VolumeMounts = append(podAdditions.VolumeMounts, gitConfigAdditions.VolumeMounts...)
if gitConfigAdditions != nil {
podAdditions.Volumes = append(podAdditions.Volumes, gitConfigAdditions.Volumes...)
podAdditions.VolumeMounts = append(podAdditions.VolumeMounts, gitConfigAdditions.VolumeMounts...)
}

// Grab the credentials additions
if len(credentials) > 0 {
Expand Down
56 changes: 38 additions & 18 deletions pkg/provision/workspace/automount/secret.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,45 +19,52 @@ import (
"path"

"github.com/devfile/devworkspace-operator/pkg/provision/sync"
v1 "k8s.io/api/core/v1"
corev1 "k8s.io/api/core/v1"
k8sclient "sigs.k8s.io/controller-runtime/pkg/client"

"github.com/devfile/devworkspace-operator/apis/controller/v1alpha1"
"github.com/devfile/devworkspace-operator/pkg/common"
"github.com/devfile/devworkspace-operator/pkg/constants"
)

func getDevWorkspaceSecrets(namespace string, api sync.ClusterAPI) (*v1alpha1.PodAdditions, []v1.EnvFromSource, error) {
secrets := &v1.SecretList{}
func getDevWorkspaceSecrets(namespace string, api sync.ClusterAPI) (*v1alpha1.PodAdditions, []corev1.EnvFromSource, error) {
secrets := &corev1.SecretList{}
if err := api.Client.List(api.Ctx, secrets, k8sclient.InNamespace(namespace), k8sclient.MatchingLabels{
constants.DevWorkspaceMountLabel: "true",
}); err != nil {
return nil, nil, err
}
podAdditions := &v1alpha1.PodAdditions{}
var additionalEnvVars []v1.EnvFromSource
var additionalEnvVars []corev1.EnvFromSource
for _, secret := range secrets.Items {
mountAs := secret.Annotations[constants.DevWorkspaceMountAsAnnotation]
if mountAs == "env" {
additionalEnvVars = append(additionalEnvVars, getAutoMountSecretEnvFromSource(secret.Name))
continue
}
mountPath := secret.Annotations[constants.DevWorkspaceMountPathAnnotation]
if mountPath == "" {
mountPath = path.Join("/etc/", "secret/", secret.Name)
}
if mountAs == "subpath" {
podAdditions.Volumes = append(podAdditions.Volumes, GetAutoMountVolumeWithSecret(secret.Name))
podAdditions.VolumeMounts = append(podAdditions.VolumeMounts, GetAutoMountSecretSubpathVolumeMounts(mountPath, secret)...)
} else {
mountPath := secret.Annotations[constants.DevWorkspaceMountPathAnnotation]
if mountPath == "" {
mountPath = path.Join("/etc/", "secret/", secret.Name)
}
// mountAs == "file", "", or anything else (default). Don't treat invalid values as errors to avoid
// failing all workspace starts in this namespace
podAdditions.Volumes = append(podAdditions.Volumes, GetAutoMountVolumeWithSecret(secret.Name))
podAdditions.VolumeMounts = append(podAdditions.VolumeMounts, GetAutoMountSecretVolumeMount(mountPath, secret.Name))
}
}
return podAdditions, additionalEnvVars, nil
}

func GetAutoMountVolumeWithSecret(name string) v1.Volume {
func GetAutoMountVolumeWithSecret(name string) corev1.Volume {
modeReadOnly := int32(0640)
workspaceVolumeMount := v1.Volume{
workspaceVolumeMount := corev1.Volume{
Name: common.AutoMountSecretVolumeName(name),
VolumeSource: v1.VolumeSource{
Secret: &v1.SecretVolumeSource{
VolumeSource: corev1.VolumeSource{
Secret: &corev1.SecretVolumeSource{
SecretName: name,
DefaultMode: &modeReadOnly,
},
Expand All @@ -66,19 +73,32 @@ func GetAutoMountVolumeWithSecret(name string) v1.Volume {
return workspaceVolumeMount
}

func GetAutoMountSecretVolumeMount(mountPath, name string) v1.VolumeMount {
workspaceVolumeMount := v1.VolumeMount{
func GetAutoMountSecretVolumeMount(mountPath, name string) corev1.VolumeMount {
workspaceVolumeMount := corev1.VolumeMount{
Name: common.AutoMountSecretVolumeName(name),
ReadOnly: true,
MountPath: mountPath,
}
return workspaceVolumeMount
}

func getAutoMountSecretEnvFromSource(name string) v1.EnvFromSource {
return v1.EnvFromSource{
SecretRef: &v1.SecretEnvSource{
LocalObjectReference: v1.LocalObjectReference{
func GetAutoMountSecretSubpathVolumeMounts(mountPath string, secret corev1.Secret) []corev1.VolumeMount {
var workspaceVolumeMounts []corev1.VolumeMount
for secretKey := range secret.Data {
workspaceVolumeMounts = append(workspaceVolumeMounts, corev1.VolumeMount{
Name: common.AutoMountSecretVolumeName(secret.Name),
ReadOnly: true,
MountPath: path.Join(mountPath, secretKey),
SubPath: secretKey,
})
}
return workspaceVolumeMounts
}

func getAutoMountSecretEnvFromSource(name string) corev1.EnvFromSource {
return corev1.EnvFromSource{
SecretRef: &corev1.SecretEnvSource{
LocalObjectReference: corev1.LocalObjectReference{
Name: name,
},
},
Expand Down