Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Automount volumes #6698

Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,6 @@ When running `odo dev` on cluster, if you make changes to the Devfile affecting

Pre-Stop events defined in the Devfile are not triggered when running `odo dev` on Podman.

## Auto-mounting volumes

[Auto-mounting volumes](/docs/user-guides/advanced/automounting-volumes) is not supported when working on Podman.
36 changes: 11 additions & 25 deletions docs/website/docs/user-guides/advanced/air-gap-environment.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,31 +127,17 @@ commands:
id: install
```

You can also provide additional environment variables to the container:
You can also provide additional environment variables to the container, by creating an [auto-mounted configmap](/docs/user-guides/advanced/automounting-volumes):

```yaml
[...]
components:
- container:
args:
- tail
- -f
- /dev/null
endpoints:
- name: http-node
targetPort: 3000
- exposure: none
name: debug
targetPort: 5858
env:
- name: DEBUG_PORT
value: "5858"
# highlight-start
- name: npm_config_registry
value: https://your_local_registry
# highlight-end
image: your_secure_registry/nodejs-16:latest
memoryLimit: 1024Mi
mountSources: true
name: runtime
apiVersion: v1
kind: ConfigMap
metadata:
name: proxy-config
labels:
devfile.io/auto-mount: "true"
annotations:
devfile.io/mount-as: env
data:
npm_config_registry: "https://your_local_registry"
```
39 changes: 39 additions & 0 deletions docs/website/docs/user-guides/advanced/automounting-volumes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
---
title: Automounting Volumes
sidebar_position: 8
---

Existing [ConfigMaps](https://kubernetes.io/docs/concepts/configuration/configmap/), [Secrets](https://kubernetes.io/docs/concepts/configuration/secret/), and [Persistent Volume Claims](https://kubernetes.io/docs/concepts/storage/persistent-volumes/) on the cluster can be mounted automatically to all containers created by `odo`. These resources can be configured by applying the appropriate labels.

To mark a resource for mounting to containers created by `odo`, apply the following label to the resource:

```yaml
metadata:
labels:
devfile.io/auto-mount: "true"
```

By default, resources will be mounted based on the resource name:

- Secrets will be mounted to `/etc/secret/<secret-name>`

- Configmaps will be mounted to `/etc/config/<configmap-name>`

- Persistent volume claims will be mounted to `/tmp/<pvc-name>`
rm3l marked this conversation as resolved.
Show resolved Hide resolved

Mounting resources can be additionally configured via annotations:

- `devfile.io/mount-path`: configure where the resource should be mounted

- `devfile.io/mount-as`: for secrets and configmaps only, configure how the resource should be mounted to the container

- If `devfile.io/mount-as: file`, the configmap/secret will be mounted as files within the mount path. This is the default behavior.

- If `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 `devfile.io/mount-as: env`, the keys and values in the configmap/secret will be mounted as environment variables in all containers.

When `file` is used, the configmap is mounted as a directory within the containers, 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 containers without a restart.

- `devfile.io/read-only`: for persistent volume claims, mount the resource as read-only

3 changes: 3 additions & 0 deletions pkg/configAutomount/doc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
// configAutomount package provides functions to work with automounted configuration resources (Configmap, Secret, PVC)
// Specified at https://github.com/devfile/devworkspace-operator/blob/main/docs/additional-configuration.adoc#automatically-mounting-volumes-configmaps-and-secrets
package configAutomount
38 changes: 38 additions & 0 deletions pkg/configAutomount/interface.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package configAutomount

type MountAs int
type VolumeType int

const (
MountAsFile MountAs = iota + 1
MountAsSubpath
MountAsEnv
)

const (
VolumeTypePVC VolumeType = iota + 1
VolumeTypeConfigmap
VolumeTypeSecret
)

type AutomountInfo struct {
// VolumeType gives the type of the volume (PVC, Secret, ConfigMap)
VolumeType VolumeType
// VolumeName is the name of the resource to mount
VolumeName string
// MountPath indicates on which path to mount the volume (empty if MountAs is Env)
MountPath string
// MountAs indicates how to mount the volume
// - File: by default
// - Env: As environment variables (for Secret and Configmap)
// - Subpath: As individual files in specific paths (For Secret and ConfigMap). Keys must be provided
MountAs MountAs
// ReadOnly indicates to mount the volume as Read-Only
ReadOnly bool
// Keys defines the list of keys to mount when MountAs is Subpath
Keys []string
}

type Client interface {
GetAutomountingVolumes() ([]AutomountInfo, error)
}
160 changes: 160 additions & 0 deletions pkg/configAutomount/kubernetes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package configAutomount

import (
"path/filepath"
"sort"

"github.com/redhat-developer/odo/pkg/kclient"
)

const (
labelMountName = "devfile.io/auto-mount"
labelMountValue = "true"

annotationMountPathName = "devfile.io/mount-path"
annotationMountAsName = "devfile.io/mount-as"
annotationReadOnlyName = "devfile.io/read-only"
)

type KubernetesClient struct {
kubeClient kclient.ClientInterface
}

func NewKubernetesClient(kubeClient kclient.ClientInterface) KubernetesClient {
return KubernetesClient{
kubeClient: kubeClient,
}
}

func (o KubernetesClient) GetAutomountingVolumes() ([]AutomountInfo, error) {
var result []AutomountInfo

pvcs, err := o.getAutomountingPVCs()
if err != nil {
return nil, err
}
result = append(result, pvcs...)

secrets, err := o.getAutomountingSecrets()
if err != nil {
return nil, err
}
result = append(result, secrets...)

cms, err := o.getAutomountingConfigmaps()
if err != nil {
return nil, err
}
result = append(result, cms...)

return result, nil
}

func (o KubernetesClient) getAutomountingPVCs() ([]AutomountInfo, error) {
pvcs, err := o.kubeClient.ListPVCs(labelMountName + "=" + labelMountValue)
if err != nil {
return nil, err
}

var result []AutomountInfo
for _, pvc := range pvcs {
mountPath := filepath.ToSlash(filepath.Join("/", "tmp", pvc.Name))
if val, found := getMountPathFromAnnotation(pvc.Annotations); found {
mountPath = val
}
result = append(result, AutomountInfo{
VolumeType: VolumeTypePVC,
VolumeName: pvc.Name,
MountPath: mountPath,
MountAs: MountAsFile,
ReadOnly: pvc.Annotations[annotationReadOnlyName] == "true",
})
}
return result, nil
}

func (o KubernetesClient) getAutomountingSecrets() ([]AutomountInfo, error) {
secrets, err := o.kubeClient.ListSecrets(labelMountName + "=" + labelMountValue)
if err != nil {
return nil, err
}

var result []AutomountInfo
for _, secret := range secrets {
mountAs := getMountAsFromAnnotation(secret.Annotations)
mountPath := filepath.ToSlash(filepath.Join("/", "etc", "secret", secret.Name))
var keys []string
if val, found := getMountPathFromAnnotation(secret.Annotations); found {
mountPath = val
}
if mountAs == MountAsEnv {
mountPath = ""
}
if mountAs == MountAsSubpath {
for k := range secret.Data {
keys = append(keys, k)
}
sort.Strings(keys)
}
result = append(result, AutomountInfo{
VolumeType: VolumeTypeSecret,
VolumeName: secret.Name,
MountPath: mountPath,
MountAs: mountAs,
ReadOnly: secret.Annotations[annotationReadOnlyName] == "true",
Keys: keys,
})
}
return result, nil
}

func (o KubernetesClient) getAutomountingConfigmaps() ([]AutomountInfo, error) {
cms, err := o.kubeClient.ListConfigMaps(labelMountName + "=" + labelMountValue)
if err != nil {
return nil, err
}

var result []AutomountInfo
for _, cm := range cms {
mountAs := getMountAsFromAnnotation(cm.Annotations)
mountPath := filepath.ToSlash(filepath.Join("/", "etc", "config", cm.Name))
var keys []string
if val, found := getMountPathFromAnnotation(cm.Annotations); found {
mountPath = val
}
if mountAs == MountAsEnv {
mountPath = ""
}
if mountAs == MountAsSubpath {
for k := range cm.Data {
keys = append(keys, k)
}
sort.Strings(keys)
}
result = append(result, AutomountInfo{
VolumeType: VolumeTypeConfigmap,
VolumeName: cm.Name,
MountPath: mountPath,
MountAs: mountAs,
ReadOnly: cm.Annotations[annotationReadOnlyName] == "true",
Keys: keys,
})
}
return result, nil
}

func getMountPathFromAnnotation(annotations map[string]string) (string, bool) {
val, found := annotations[annotationMountPathName]
return val, found
}

func getMountAsFromAnnotation(annotations map[string]string) MountAs {
switch annotations[annotationMountAsName] {
case "subpath":
return MountAsSubpath
case "env":
return MountAsEnv
default:
return MountAsFile
}
}
Loading