// // Copyright (c) 2019-2023 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 usernamespace import ( "context" "sync" "github.com/devfile/devworkspace-operator/pkg/infrastructure" projectv1 "github.com/openshift/api/project/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" ) const ( workspaceNamespaceOwnerUidLabel string = "che.eclipse.org/workspace-namespace-owner-uid" cheNameLabel string = "che.eclipse.org/che-name" cheNamespaceLabel string = "che.eclipse.org/che-namespace" chePartOfLabel string = "app.kubernetes.io/part-of" chePartOfLabelValue string = "che.eclipse.org" cheComponentLabel string = "app.kubernetes.io/component" cheComponentLabelValue string = "workspaces-namespace" cheUsernameAnnotation string = "che.eclipse.org/username" ) type namespaceCache struct { client client.Client knownNamespaces map[string]namespaceInfo lock sync.Mutex } type namespaceInfo struct { IsWorkspaceNamespace bool Username string CheCluster *types.NamespacedName } func NewNamespaceCache(client client.Client) *namespaceCache { return &namespaceCache{ client: client, knownNamespaces: map[string]namespaceInfo{}, lock: sync.Mutex{}, } } func (c *namespaceCache) GetNamespaceInfo(ctx context.Context, namespace string) (*namespaceInfo, error) { c.lock.Lock() defer c.lock.Unlock() for { val, contains := c.knownNamespaces[namespace] if contains { return &val, nil } else { existing, err := c.examineNamespaceUnsafe(ctx, namespace) if err != nil { return nil, err } else if existing == nil { return nil, nil } } } } func (c *namespaceCache) ExamineNamespace(ctx context.Context, ns string) (*namespaceInfo, error) { c.lock.Lock() defer c.lock.Unlock() return c.examineNamespaceUnsafe(ctx, ns) } func (c *namespaceCache) GetAllKnownNamespaces() []string { c.lock.Lock() defer c.lock.Unlock() ret := make([]string, len(c.knownNamespaces)) i := 0 for k := range c.knownNamespaces { ret[i] = k i = i + 1 } return ret } func (c *namespaceCache) examineNamespaceUnsafe(ctx context.Context, ns string) (*namespaceInfo, error) { var obj client.Object if infrastructure.IsOpenShift() { obj = &projectv1.Project{} } else { obj = &corev1.Namespace{} } if err := c.client.Get(ctx, client.ObjectKey{Name: ns}, obj); err != nil { if errors.IsNotFound(err) { delete(c.knownNamespaces, ns) return nil, nil } return nil, err } var namespace = obj.(metav1.Object) if namespace.GetDeletionTimestamp() != nil { delete(c.knownNamespaces, ns) return nil, nil } labels := namespace.GetLabels() if labels == nil { labels = map[string]string{} } annotations := namespace.GetAnnotations() if annotations == nil { annotations = map[string]string{} } // ownerUid is the legacy label that we used to use. Let's not break the existing workspace namespaces and still // recognize it ownerUid := labels[workspaceNamespaceOwnerUidLabel] cheName := labels[cheNameLabel] cheNamespace := labels[cheNamespaceLabel] partOfLabel := labels[chePartOfLabel] componentLabel := labels[cheComponentLabel] username := annotations[cheUsernameAnnotation] ret := namespaceInfo{ IsWorkspaceNamespace: ownerUid != "" || (partOfLabel == chePartOfLabelValue && componentLabel == cheComponentLabelValue), Username: username, CheCluster: &types.NamespacedName{ Name: cheName, Namespace: cheNamespace, }, } c.knownNamespaces[ns] = ret return &ret, nil }