Skip to content

Commit

Permalink
Merge b3f1a93 into e6a7f02
Browse files Browse the repository at this point in the history
  • Loading branch information
feloy authored Apr 12, 2023
2 parents e6a7f02 + b3f1a93 commit d991a2f
Show file tree
Hide file tree
Showing 36 changed files with 1,788 additions and 194 deletions.
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:
controller.devfile.io/mount-to-containers: "true"
annotations:
controller.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:
controller.devfile.io/mount-to-containers: "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>`

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 container

- 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.

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.

- `controller.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 = "controller.devfile.io/mount-to-containers"
labelMountValue = "true"

annotationMountPathName = "controller.devfile.io/mount-path"
annotationMountAsName = "controller.devfile.io/mount-as"
annotationReadOnlyName = "controller.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

0 comments on commit d991a2f

Please sign in to comment.