Skip to content

Commit 4ea4873

Browse files
committed
feat: Allow external DWOC to be merged with internal DWOC
Allow specifying an external DWOC that gets merged with the workspace's internal DWOC. The external DWOC's name and namespace are specified with the `controller.devfile.io/devworkspace-config` DevWorkspace attribute, which has the following structure: attributes: controller.devfile.io/devworkspace-config: name: <string> namespace: <string> Part of eclipse-che/che#21405 Signed-off-by: Andrew Obuchowicz <[email protected]>
1 parent c97fbbb commit 4ea4873

File tree

7 files changed

+349
-5
lines changed

7 files changed

+349
-5
lines changed

apis/controller/v1alpha1/devworkspaceoperatorconfig_types.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,16 @@ type StorageSizes struct {
7979
PerWorkspace *resource.Quantity `json:"perWorkspace,omitempty"`
8080
}
8181

82+
//TODO: Should this be here or in another file?
83+
// Allows specifying an external DevWorkspace-Operator configuration
84+
// which will merged with the workspace's DevWorkspace-Operator configuration.
85+
type ExternalConfig struct {
86+
// The metadata.name of the external DevWorkspace-Operator configuration.
87+
Name string `json:"name,omitempty"`
88+
// The metadata.namespace of the external DevWorkspace-Operator configuration.
89+
Namespace string `json:"namespace,omitempty"`
90+
}
91+
8292
type WorkspaceConfig struct {
8393
// ImagePullPolicy defines the imagePullPolicy used for containers in a DevWorkspace
8494
// For additional information, see Kubernetes documentation for imagePullPolicy. If

apis/controller/v1alpha1/zz_generated.deepcopy.go

Lines changed: 15 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

controllers/workspace/devworkspace_controller.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,12 @@ func (r *DevWorkspaceReconciler) Reconcile(ctx context.Context, req ctrl.Request
245245
wsDefaults.ApplyDefaultTemplate(workspace)
246246
}
247247

248+
// Merge workspace's DWOC with an external, one if it exists
249+
err = config.ApplyExternalDWOCConfig(workspace, clusterAPI.Client)
250+
if err != nil {
251+
reqLogger.Error(err, "Unable to apply external DevWorkspace-Operator configuration")
252+
}
253+
248254
flattenedWorkspace, warnings, err := flatten.ResolveDevWorkspace(&workspace.Spec.Template, flattenHelpers)
249255
if err != nil {
250256
return r.failWorkspace(workspace, fmt.Sprintf("Error processing devfile: %s", err), metrics.ReasonBadRequest, reqLogger, &reconcileStatus)

pkg/config/common_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,3 +68,13 @@ func buildConfig(config *v1alpha1.OperatorConfiguration) *v1alpha1.DevWorkspaceO
6868
Config: config,
6969
}
7070
}
71+
72+
func buildExternalConfig(config *v1alpha1.OperatorConfiguration) *v1alpha1.DevWorkspaceOperatorConfig {
73+
return &v1alpha1.DevWorkspaceOperatorConfig{
74+
ObjectMeta: metav1.ObjectMeta{
75+
Name: ExternalConfigName,
76+
Namespace: ExternalConfigNamespace,
77+
},
78+
Config: config,
79+
}
80+
}

pkg/config/sync.go

Lines changed: 199 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,24 +18,30 @@ package config
1818
import (
1919
"context"
2020
"fmt"
21+
"reflect"
22+
"sort"
2123
"strings"
2224
"sync"
2325

26+
dw "github.com/devfile/api/v2/pkg/apis/workspaces/v1alpha2"
27+
"github.com/devfile/devworkspace-operator/apis/controller/v1alpha1"
28+
controller "github.com/devfile/devworkspace-operator/apis/controller/v1alpha1"
2429
"github.com/devfile/devworkspace-operator/pkg/config/proxy"
30+
"github.com/devfile/devworkspace-operator/pkg/constants"
31+
"github.com/devfile/devworkspace-operator/pkg/infrastructure"
2532
routeV1 "github.com/openshift/api/route/v1"
2633
k8sErrors "k8s.io/apimachinery/pkg/api/errors"
2734
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2835
"k8s.io/apimachinery/pkg/types"
2936
ctrl "sigs.k8s.io/controller-runtime"
3037
crclient "sigs.k8s.io/controller-runtime/pkg/client"
31-
32-
controller "github.com/devfile/devworkspace-operator/apis/controller/v1alpha1"
33-
"github.com/devfile/devworkspace-operator/pkg/infrastructure"
3438
)
3539

3640
const (
37-
OperatorConfigName = "devworkspace-operator-config"
38-
openShiftTestRouteName = "devworkspace-controller-test-route"
41+
OperatorConfigName = "devworkspace-operator-config"
42+
openShiftTestRouteName = "devworkspace-controller-test-route"
43+
ExternalConfigName = "external-config-name"
44+
ExternalConfigNamespace = "external-config-namespace"
3945
)
4046

4147
var (
@@ -55,6 +61,53 @@ func SetConfigForTesting(config *controller.OperatorConfiguration) {
5561
updatePublicConfig()
5662
}
5763

64+
func ApplyExternalDWOCConfig(workspace *dw.DevWorkspace, client crclient.Client) (err error) {
65+
66+
if !workspace.Spec.Template.Attributes.Exists(constants.ExternalDevWorkspaceConfiguration) {
67+
return nil
68+
}
69+
70+
ExternalDWOCMeta := v1alpha1.ExternalConfig{}
71+
72+
err = workspace.Spec.Template.Attributes.GetInto(constants.ExternalDevWorkspaceConfiguration, &ExternalDWOCMeta)
73+
if err != nil {
74+
return fmt.Errorf("failed to read attribute %s in DevWorkspace attributes: %w", constants.ExternalDevWorkspaceConfiguration, err)
75+
}
76+
77+
if ExternalDWOCMeta.Name == "" {
78+
return fmt.Errorf("'name' must be set for attribute %s in DevWorkspace attributes", constants.ExternalDevWorkspaceConfiguration)
79+
}
80+
81+
if ExternalDWOCMeta.Namespace == "" {
82+
return fmt.Errorf("'namespace' must be set for attribute %s in DevWorkspace attributes", constants.ExternalDevWorkspaceConfiguration)
83+
}
84+
85+
externalDWOC := &controller.DevWorkspaceOperatorConfig{}
86+
namespacedName := types.NamespacedName{
87+
Name: ExternalDWOCMeta.Name,
88+
Namespace: ExternalDWOCMeta.Namespace,
89+
}
90+
91+
err = client.Get(context.TODO(), namespacedName, externalDWOC)
92+
if err != nil {
93+
return fmt.Errorf("could not fetch external DWOC with name '%s' in namespace '%s': %w", ExternalDWOCMeta.Name, ExternalDWOCMeta.Namespace, err)
94+
}
95+
96+
// TODO: It might be better to just always merge rather than suffering performance hit in configsAlreadyMerged() (or maintaing configsAlreadyMerged if we don't use reflect.DeepEqual)
97+
if !configsAlreadyMerged(externalDWOC.Config, internalConfig) {
98+
mergeInteralConfigWithExternal(externalDWOC.Config)
99+
}
100+
101+
return nil
102+
}
103+
104+
func mergeInteralConfigWithExternal(externalConfig *controller.OperatorConfiguration) {
105+
configMutex.Lock()
106+
defer configMutex.Unlock()
107+
mergeConfig(externalConfig, internalConfig)
108+
updatePublicConfig()
109+
}
110+
58111
func SetupControllerConfig(client crclient.Client) error {
59112
if internalConfig != nil {
60113
return fmt.Errorf("internal controller configuration is already set up")
@@ -185,6 +238,147 @@ func discoverRouteSuffix(client crclient.Client) (string, error) {
185238
return host, nil
186239
}
187240

241+
// TODO: Improve variable names?
242+
// Returns true if 'from' has already been merged with 'to'.
243+
// The two configs are considered merged if all fields that are set in 'from' have the same value in 'to'
244+
func configsAlreadyMerged(from, to *controller.OperatorConfiguration) bool {
245+
if from == nil {
246+
return true
247+
}
248+
if from.EnableExperimentalFeatures != nil {
249+
250+
if to.EnableExperimentalFeatures == nil {
251+
return false
252+
}
253+
254+
if *to.EnableExperimentalFeatures != *from.EnableExperimentalFeatures {
255+
return false
256+
}
257+
}
258+
if from.Routing != nil {
259+
if to.Routing == nil {
260+
return false
261+
}
262+
if from.Routing.DefaultRoutingClass != "" && to.Routing.DefaultRoutingClass != from.Routing.DefaultRoutingClass {
263+
return false
264+
}
265+
if from.Routing.ClusterHostSuffix != "" && to.Routing.ClusterHostSuffix != from.Routing.ClusterHostSuffix {
266+
return false
267+
}
268+
if from.Routing.ProxyConfig != nil {
269+
if to.Routing.ProxyConfig == nil {
270+
return false
271+
}
272+
273+
if from.Routing.ProxyConfig.HttpProxy != "" && to.Routing.ProxyConfig.HttpProxy != from.Routing.ProxyConfig.HttpProxy {
274+
return false
275+
}
276+
277+
if from.Routing.ProxyConfig.HttpsProxy != "" && to.Routing.ProxyConfig.HttpsProxy != from.Routing.ProxyConfig.HttpsProxy {
278+
return false
279+
}
280+
281+
if from.Routing.ProxyConfig.NoProxy != "" && to.Routing.ProxyConfig.NoProxy != from.Routing.ProxyConfig.NoProxy {
282+
return false
283+
}
284+
}
285+
}
286+
if from.Workspace != nil {
287+
if to.Workspace == nil {
288+
return false
289+
}
290+
if from.Workspace.StorageClassName != nil {
291+
if to.Workspace.StorageClassName == nil {
292+
return false
293+
}
294+
295+
if *to.Workspace.StorageClassName != *from.Workspace.StorageClassName {
296+
return false
297+
}
298+
}
299+
if from.Workspace.PVCName != "" && to.Workspace.PVCName != from.Workspace.PVCName {
300+
return false
301+
}
302+
if from.Workspace.ImagePullPolicy != "" && to.Workspace.ImagePullPolicy != from.Workspace.ImagePullPolicy {
303+
return false
304+
}
305+
if from.Workspace.IdleTimeout != "" && to.Workspace.IdleTimeout != from.Workspace.IdleTimeout {
306+
return false
307+
}
308+
if from.Workspace.ProgressTimeout != "" && to.Workspace.ProgressTimeout != from.Workspace.ProgressTimeout {
309+
return false
310+
}
311+
if from.Workspace.IgnoredUnrecoverableEvents != nil && to.Workspace.IgnoredUnrecoverableEvents != nil {
312+
313+
if len(from.Workspace.IgnoredUnrecoverableEvents) != len(to.Workspace.IgnoredUnrecoverableEvents) {
314+
return false
315+
}
316+
317+
sort.Strings(to.Workspace.IgnoredUnrecoverableEvents)
318+
sort.Strings(from.Workspace.IgnoredUnrecoverableEvents)
319+
320+
for i := range from.Workspace.IgnoredUnrecoverableEvents {
321+
if from.Workspace.IgnoredUnrecoverableEvents[i] != to.Workspace.IgnoredUnrecoverableEvents[i] {
322+
return false
323+
}
324+
}
325+
}
326+
if from.Workspace.CleanupOnStop != nil {
327+
if to.Workspace.CleanupOnStop == nil {
328+
return false
329+
}
330+
331+
if *to.Workspace.CleanupOnStop != *from.Workspace.CleanupOnStop {
332+
return false
333+
}
334+
}
335+
if from.Workspace.PodSecurityContext != nil {
336+
if to.Workspace.PodSecurityContext == nil {
337+
return false
338+
}
339+
340+
// TODO: Using reflect.DeepEqual could potentially really degrade performance
341+
if !reflect.DeepEqual(from.Workspace.PodSecurityContext, to.Workspace.PodSecurityContext) {
342+
return false
343+
}
344+
}
345+
if from.Workspace.DefaultStorageSize != nil {
346+
if to.Workspace.DefaultStorageSize == nil {
347+
return false
348+
}
349+
if from.Workspace.DefaultStorageSize.Common != nil {
350+
if to.Workspace.DefaultStorageSize.Common == nil {
351+
return false
352+
}
353+
if from.Workspace.DefaultStorageSize.Common.Cmp(*to.Workspace.DefaultStorageSize.Common) != 0 {
354+
return false
355+
}
356+
357+
}
358+
if from.Workspace.DefaultStorageSize.PerWorkspace != nil {
359+
if to.Workspace.DefaultStorageSize.PerWorkspace == nil {
360+
return false
361+
}
362+
if from.Workspace.DefaultStorageSize.PerWorkspace.Cmp(*to.Workspace.DefaultStorageSize.PerWorkspace) != 0 {
363+
return false
364+
}
365+
}
366+
367+
}
368+
if from.Workspace.DefaultTemplate != nil {
369+
if to.Workspace.DefaultTemplate == nil {
370+
return false
371+
}
372+
373+
// TODO: Using reflect.DeepEqual could potentially really degrade performance
374+
if !reflect.DeepEqual(from.Workspace.DefaultTemplate, to.Workspace.DefaultTemplate) {
375+
return false
376+
}
377+
}
378+
}
379+
return true
380+
}
381+
188382
func mergeConfig(from, to *controller.OperatorConfiguration) {
189383
if to == nil {
190384
to = &controller.OperatorConfiguration{}

0 commit comments

Comments
 (0)