diff --git a/pkg/constants/metadata.go b/pkg/constants/metadata.go index aab78bf6a..30c95241f 100644 --- a/pkg/constants/metadata.go +++ b/pkg/constants/metadata.go @@ -77,4 +77,8 @@ const ( // Only secrets with 'true' value will be mount as pull secret // Should be assigned to secrets with type docker config types (kubernetes.io/dockercfg and kubernetes.io/dockerconfigjson) DevWorkspacePullSecretLabel = "controller.devfile.io/devworkspace_pullsecret" + + // NamespacedConfigLabelKey is a label applied to configmaps to mark them as a configuration for all DevWorkspaces in + // the current namespace. + NamespacedConfigLabelKey = "controller.devfile.io/namespaced-config" ) diff --git a/pkg/provision/config/config.go b/pkg/provision/config/config.go new file mode 100644 index 000000000..ad03c7812 --- /dev/null +++ b/pkg/provision/config/config.go @@ -0,0 +1,71 @@ +// +// Copyright (c) 2019-2021 Red Hat, Inc. +// This program and the accompanying materials are made +// available under the terms of the Eclipse Public License 2.0 +// which is available at https://www.eclipse.org/legal/epl-2.0/ +// +// SPDX-License-Identifier: EPL-2.0 +// +// Contributors: +// Red Hat, Inc. - initial API and implementation +// + +package config + +import ( + "fmt" + "strings" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/labels" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/devfile/devworkspace-operator/controllers/workspace/provision" + "github.com/devfile/devworkspace-operator/pkg/constants" +) + +const ( + commonPVCSizeKey = "commonPVCSize" +) + +type NamespacedConfig struct { + CommonPVCSize string +} + +// ReadNamespacedConfig reads the per-namespace DevWorkspace configmap and returns it as a struct. If there are +// no valid configmaps in the specified namespace, returns (nil, nil). If there are multiple configmaps with the +// per-namespace configmap label, returns an error. +func ReadNamespacedConfig(namespace string, api provision.ClusterAPI) (*NamespacedConfig, error) { + cmList := &corev1.ConfigMapList{} + labelSelector, err := labels.Parse(fmt.Sprintf("%s=true", constants.NamespacedConfigLabelKey)) + if err != nil { + return nil, err + } + selector := &client.ListOptions{ + Namespace: namespace, + LabelSelector: labelSelector, + } + err = api.Client.List(api.Ctx, cmList, selector) + if err != nil { + return nil, err + } + cms := cmList.Items + if len(cms) == 0 { + return nil, nil + } else if len(cms) > 1 { + var cmNames []string + for _, cm := range cms { + cmNames = append(cmNames, cm.Name) + } + return nil, fmt.Errorf("multiple per-namespace configs found: %s", strings.Join(cmNames, ", ")) + } + + cm := cms[0] + if cm.Data == nil { + return nil, nil + } + + return &NamespacedConfig{ + CommonPVCSize: cm.Data[commonPVCSizeKey], + }, nil +} diff --git a/pkg/provision/storage/commonStorage_test.go b/pkg/provision/storage/commonStorage_test.go index c9fc03487..439691d77 100644 --- a/pkg/provision/storage/commonStorage_test.go +++ b/pkg/provision/storage/commonStorage_test.go @@ -101,7 +101,7 @@ func TestRewriteContainerVolumeMountsForCommonStorageClass(t *testing.T) { tests := loadAllTestCasesOrPanic(t, "testdata/common-storage") setupControllerCfg() commonStorage := CommonStorageProvisioner{} - commonPVC, err := getCommonPVCSpec("test-namespace") + commonPVC, err := getCommonPVCSpec("test-namespace", "1Gi") if err != nil { t.Fatalf("Failure during setup: %s", err) } diff --git a/pkg/provision/storage/shared.go b/pkg/provision/storage/shared.go index d00f7c991..3fe2e4ff5 100644 --- a/pkg/provision/storage/shared.go +++ b/pkg/provision/storage/shared.go @@ -24,14 +24,15 @@ import ( "github.com/devfile/devworkspace-operator/pkg/constants" devfileConstants "github.com/devfile/devworkspace-operator/pkg/library/constants" containerlib "github.com/devfile/devworkspace-operator/pkg/library/container" + nsconfig "github.com/devfile/devworkspace-operator/pkg/provision/config" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) -func getCommonPVCSpec(namespace string) (*corev1.PersistentVolumeClaim, error) { - pvcStorageQuantity, err := resource.ParseQuantity(constants.PVCStorageSize) +func getCommonPVCSpec(namespace string, size string) (*corev1.PersistentVolumeClaim, error) { + pvcStorageQuantity, err := resource.ParseQuantity(size) if err != nil { return nil, err } @@ -79,7 +80,16 @@ func needsStorage(workspace *dw.DevWorkspaceTemplateSpec) bool { } func syncCommonPVC(namespace string, clusterAPI provision.ClusterAPI) (*corev1.PersistentVolumeClaim, error) { - pvc, err := getCommonPVCSpec(namespace) + namespacedConfig, err := nsconfig.ReadNamespacedConfig(namespace, clusterAPI) + if err != nil { + return nil, fmt.Errorf("failed to read namespace-specific configuration: %w", err) + } + pvcSize := constants.PVCStorageSize + if namespacedConfig != nil && namespacedConfig.CommonPVCSize != "" { + pvcSize = namespacedConfig.CommonPVCSize + } + + pvc, err := getCommonPVCSpec(namespace, pvcSize) if err != nil { return nil, err }