feat: Allow to configure user custom roles (#1663)

* feat: Allow to configure user custom roles without duplicating default ones

Signed-off-by: Anatolii Bazko <abazko@redhat.com>
pull/1670/head
Anatolii Bazko 2023-04-21 14:52:05 +03:00 committed by GitHub
parent 077a358355
commit 4bcc78a27a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 929 additions and 915 deletions

View File

@ -364,6 +364,9 @@ func TestConvertFrom(t *testing.T) {
SecondsOfInactivityBeforeIdling: pointer.Int32Ptr(1800),
SecondsOfRunBeforeIdling: pointer.Int32Ptr(-1),
MaxNumberOfRunningWorkspacesPerUser: pointer.Int64Ptr(10),
User: &chev2.UserConfiguration{
ClusterRoles: []string{"ClusterRoles_1", "ClusterRoles_2"},
},
},
ContainerRegistry: chev2.CheClusterContainerRegistry{
Hostname: "AirGapContainerRegistryHostname",
@ -506,6 +509,7 @@ func TestConvertFrom(t *testing.T) {
},
})
assert.Equal(t, checlusterv1.Spec.Server.WorkspacesDefaultPlugins, []chev1.WorkspacesDefaultPlugins{{Editor: "Editor", Plugins: []string{"Plugins_1", "Plugins_2"}}})
assert.Equal(t, checlusterv1.Spec.Server.CheWorkspaceClusterRole, "ClusterRoles_1,ClusterRoles_2")
assert.Equal(t, checlusterv1.Spec.Storage.PvcStrategy, "PvcStrategy")
assert.Equal(t, checlusterv1.Spec.Storage.PvcClaimSize, "StorageClaimSize")

View File

@ -161,7 +161,7 @@ func TestConvertTo(t *testing.T) {
CheLogLevel: "CheLogLevel",
CheDebug: "true",
CheClusterRoles: "CheClusterRoles_1,CheClusterRoles_2",
CheWorkspaceClusterRole: "CheWorkspaceClusterRole",
CheWorkspaceClusterRole: "ClusterRoles_1,ClusterRoles_2",
WorkspaceNamespaceDefault: "WorkspaceNamespaceDefault",
AllowAutoProvisionUserNamespace: pointer.BoolPtr(true),
WorkspaceDefaultEditor: "WorkspaceDefaultEditor",
@ -413,6 +413,7 @@ func TestConvertTo(t *testing.T) {
Editor: "Editor",
Plugins: []string{"Plugin_1,Plugin_2"},
}})
assert.Equal(t, checlusterv2.Spec.DevEnvironments.User.ClusterRoles, []string{"CheClusterRoles_1", "CheClusterRoles_2"})
assert.Equal(t, checlusterv2.Spec.Components.Dashboard.Deployment.Containers[0].Name, defaults.GetCheFlavor()+"-dashboard")
assert.Equal(t, checlusterv2.Spec.Components.Dashboard.Deployment.Containers[0].Image, "DashboardImage")

View File

@ -281,6 +281,12 @@ func TestRoundConvertCheClusterV2(t *testing.T) {
Effect: "Effect",
}},
MaxNumberOfRunningWorkspacesPerUser: pointer.Int64Ptr(10),
User: &chev2.UserConfiguration{
ClusterRoles: []string{
"ClusterRoles_1",
"ClusterRoles_2",
},
},
},
ContainerRegistry: chev2.CheClusterContainerRegistry{
Hostname: "AirGapContainerRegistryHostname",

View File

@ -108,6 +108,9 @@ func (dst *CheCluster) convertFrom_Server(src *chev2.CheCluster) error {
dst.Spec.Server.AirGapContainerRegistryHostname = src.Spec.ContainerRegistry.Hostname
dst.Spec.Server.AirGapContainerRegistryOrganization = src.Spec.ContainerRegistry.Organization
dst.Spec.Server.CheClusterRoles = strings.Join(src.Spec.Components.CheServer.ClusterRoles, ",")
if src.Spec.DevEnvironments.User != nil {
dst.Spec.Server.CheWorkspaceClusterRole = strings.Join(src.Spec.DevEnvironments.User.ClusterRoles, ",")
}
dst.Spec.Server.CustomCheProperties = utils.CloneMap(src.Spec.Components.CheServer.ExtraProperties)
if src.Spec.Components.CheServer.Debug != nil {
dst.Spec.Server.CheDebug = strconv.FormatBool(*src.Spec.Components.CheServer.Debug)

View File

@ -162,6 +162,11 @@ func (src *CheCluster) convertTo_DevEnvironments(dst *chev2.CheCluster) error {
dst.Spec.DevEnvironments.DefaultComponents = src.Spec.Server.WorkspaceDefaultComponents
dst.Spec.DevEnvironments.SecondsOfInactivityBeforeIdling = src.Spec.DevWorkspace.SecondsOfInactivityBeforeIdling
dst.Spec.DevEnvironments.SecondsOfRunBeforeIdling = src.Spec.DevWorkspace.SecondsOfRunBeforeIdling
if src.Spec.Server.CheWorkspaceClusterRole != "" {
dst.Spec.DevEnvironments.User = &chev2.UserConfiguration{
ClusterRoles: strings.Split(src.Spec.Server.CheClusterRoles, ","),
}
}
if err := src.convertTo_Workspaces_Storage(dst); err != nil {
return err

View File

@ -148,6 +148,9 @@ type CheClusterDevEnvironments struct {
// +kubebuilder:validation:Minimum:=-1
// +optional
MaxNumberOfRunningWorkspacesPerUser *int64 `json:"maxNumberOfRunningWorkspacesPerUser,omitempty"`
// User configuration.
// +optional
User *UserConfiguration `json:"user,omitempty"`
}
// Che components configuration.
@ -246,13 +249,13 @@ type CheServer struct {
// +optional
// +kubebuilder:default:=false
Debug *bool `json:"debug,omitempty"`
// ClusterRoles assigned to Che ServiceAccount.
// The defaults roles are:
// - `<che-namespace>-cheworkspaces-namespaces-clusterrole`
// - `<che-namespace>-cheworkspaces-clusterrole`
// - `<che-namespace>-cheworkspaces-devworkspace-clusterrole`
// where the <che-namespace> is the namespace where the CheCluster CRD is created.
// Additional ClusterRoles assigned to Che ServiceAccount.
// Each role must have a `app.kubernetes.io/part-of=che.eclipse.org` label.
// The defaults roles are:
// - `<che-namespace>-cheworkspaces-clusterrole`
// - `<che-namespace>-cheworkspaces-namespaces-clusterrole`
// - `<che-namespace>-cheworkspaces-devworkspace-clusterrole`
// where the <che-namespace> is the namespace where the CheCluster CR is created.
// The Che Operator must already have all permissions in these ClusterRoles to grant them.
// +optional
ClusterRoles []string `json:"clusterRoles,omitempty"`
@ -411,6 +414,13 @@ type TrustedCerts struct {
GitTrustedCertsConfigMapName string `json:"gitTrustedCertsConfigMapName,omitempty"`
}
type UserConfiguration struct {
// Additional ClusterRoles assigned to the user.
// The role must have `app.kubernetes.io/part-of=che.eclipse.org` label.
// +optional
ClusterRoles []string `json:"clusterRoles,omitempty"`
}
// Configuration settings related to the workspaces persistent storage.
type WorkspaceStorage struct {
// PVC settings when using the `per-user` PVC strategy.

View File

@ -223,6 +223,11 @@ func (in *CheClusterDevEnvironments) DeepCopyInto(out *CheClusterDevEnvironments
*out = new(int64)
**out = **in
}
if in.User != nil {
in, out := &in.User, &out.User
*out = new(UserConfiguration)
(*in).DeepCopyInto(*out)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CheClusterDevEnvironments.
@ -883,6 +888,26 @@ func (in *TrustedCerts) DeepCopy() *TrustedCerts {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *UserConfiguration) DeepCopyInto(out *UserConfiguration) {
*out = *in
if in.ClusterRoles != nil {
in, out := &in.ClusterRoles, &out.ClusterRoles
*out = make([]string, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UserConfiguration.
func (in *UserConfiguration) DeepCopy() *UserConfiguration {
if in == nil {
return nil
}
out := new(UserConfiguration)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *WorkspaceDefaultPlugins) DeepCopyInto(out *WorkspaceDefaultPlugins) {
*out = *in

View File

@ -77,7 +77,7 @@ metadata:
operators.operatorframework.io/project_layout: go.kubebuilder.io/v3
repository: https://github.com/eclipse-che/che-operator
support: Eclipse Foundation
name: eclipse-che.v7.64.0-786.next
name: eclipse-che.v7.65.0-789.next
namespace: placeholder
spec:
apiservicedefinitions: {}
@ -1243,7 +1243,7 @@ spec:
minKubeVersion: 1.19.0
provider:
name: Eclipse Foundation
version: 7.64.0-786.next
version: 7.65.0-789.next
webhookdefinitions:
- admissionReviewVersions:
- v1

View File

@ -4067,13 +4067,14 @@ spec:
server.
properties:
clusterRoles:
description: 'ClusterRoles assigned to Che ServiceAccount.
The defaults roles are: - `<che-namespace>-cheworkspaces-namespaces-clusterrole`
- `<che-namespace>-cheworkspaces-clusterrole` - `<che-namespace>-cheworkspaces-devworkspace-clusterrole`
description: 'Additional ClusterRoles assigned to Che ServiceAccount.
Each role must have a `app.kubernetes.io/part-of=che.eclipse.org`
label. The defaults roles are: - `<che-namespace>-cheworkspaces-clusterrole`
- `<che-namespace>-cheworkspaces-namespaces-clusterrole`
- `<che-namespace>-cheworkspaces-devworkspace-clusterrole`
where the <che-namespace> is the namespace where the CheCluster
CRD is created. Each role must have a `app.kubernetes.io/part-of=che.eclipse.org`
label. The Che Operator must already have all permissions
in these ClusterRoles to grant them.'
CR is created. The Che Operator must already have all
permissions in these ClusterRoles to grant them.'
items:
type: string
type: array
@ -7250,6 +7251,17 @@ spec:
label.'
type: string
type: object
user:
description: User configuration.
properties:
clusterRoles:
description: Additional ClusterRoles assigned to the user.
The role must have `app.kubernetes.io/part-of=che.eclipse.org`
label.
items:
type: string
type: array
type: object
type: object
gitServices:
description: A configuration that allows users to work with remote

View File

@ -3938,12 +3938,13 @@ spec:
server.
properties:
clusterRoles:
description: 'ClusterRoles assigned to Che ServiceAccount.
The defaults roles are: - `<che-namespace>-cheworkspaces-namespaces-clusterrole`
- `<che-namespace>-cheworkspaces-clusterrole` - `<che-namespace>-cheworkspaces-devworkspace-clusterrole`
description: 'Additional ClusterRoles assigned to Che ServiceAccount.
Each role must have a `app.kubernetes.io/part-of=che.eclipse.org`
label. The defaults roles are: - `<che-namespace>-cheworkspaces-clusterrole`
- `<che-namespace>-cheworkspaces-namespaces-clusterrole`
- `<che-namespace>-cheworkspaces-devworkspace-clusterrole`
where the <che-namespace> is the namespace where the CheCluster
CRD is created. Each role must have a `app.kubernetes.io/part-of=che.eclipse.org`
label. The Che Operator must already have all permissions
CR is created. The Che Operator must already have all permissions
in these ClusterRoles to grant them.'
items:
type: string
@ -7051,6 +7052,17 @@ spec:
label.'
type: string
type: object
user:
description: User configuration.
properties:
clusterRoles:
description: Additional ClusterRoles assigned to the user.
The role must have `app.kubernetes.io/part-of=che.eclipse.org`
label.
items:
type: string
type: array
type: object
type: object
gitServices:
description: A configuration that allows users to work with remote

View File

@ -100,9 +100,7 @@ func NewReconciler(
reconcileManager.RegisterReconciler(tls.NewCertificatesReconciler())
reconcileManager.RegisterReconciler(tls.NewTlsSecretReconciler())
reconcileManager.RegisterReconciler(devworkspaceconfig.NewDevWorkspaceConfigReconciler())
reconcileManager.RegisterReconciler(rbac.NewCheServerPermissionsReconciler())
reconcileManager.RegisterReconciler(rbac.NewGatewayPermissionsReconciler())
reconcileManager.RegisterReconciler(rbac.NewWorkspacePermissionsReconciler())
// we have to expose che endpoint independently of syncing other server
// resources since che host is used for dashboard deployment and che config map

View File

@ -3957,12 +3957,13 @@ spec:
server.
properties:
clusterRoles:
description: 'ClusterRoles assigned to Che ServiceAccount.
The defaults roles are: - `<che-namespace>-cheworkspaces-namespaces-clusterrole`
- `<che-namespace>-cheworkspaces-clusterrole` - `<che-namespace>-cheworkspaces-devworkspace-clusterrole`
description: 'Additional ClusterRoles assigned to Che ServiceAccount.
Each role must have a `app.kubernetes.io/part-of=che.eclipse.org`
label. The defaults roles are: - `<che-namespace>-cheworkspaces-clusterrole`
- `<che-namespace>-cheworkspaces-namespaces-clusterrole`
- `<che-namespace>-cheworkspaces-devworkspace-clusterrole`
where the <che-namespace> is the namespace where the CheCluster
CRD is created. Each role must have a `app.kubernetes.io/part-of=che.eclipse.org`
label. The Che Operator must already have all permissions
CR is created. The Che Operator must already have all permissions
in these ClusterRoles to grant them.'
items:
type: string
@ -7070,6 +7071,17 @@ spec:
label.'
type: string
type: object
user:
description: User configuration.
properties:
clusterRoles:
description: Additional ClusterRoles assigned to the user.
The role must have `app.kubernetes.io/part-of=che.eclipse.org`
label.
items:
type: string
type: array
type: object
type: object
gitServices:
description: A configuration that allows users to work with remote

View File

@ -3952,12 +3952,13 @@ spec:
server.
properties:
clusterRoles:
description: 'ClusterRoles assigned to Che ServiceAccount.
The defaults roles are: - `<che-namespace>-cheworkspaces-namespaces-clusterrole`
- `<che-namespace>-cheworkspaces-clusterrole` - `<che-namespace>-cheworkspaces-devworkspace-clusterrole`
description: 'Additional ClusterRoles assigned to Che ServiceAccount.
Each role must have a `app.kubernetes.io/part-of=che.eclipse.org`
label. The defaults roles are: - `<che-namespace>-cheworkspaces-clusterrole`
- `<che-namespace>-cheworkspaces-namespaces-clusterrole`
- `<che-namespace>-cheworkspaces-devworkspace-clusterrole`
where the <che-namespace> is the namespace where the CheCluster
CRD is created. Each role must have a `app.kubernetes.io/part-of=che.eclipse.org`
label. The Che Operator must already have all permissions
CR is created. The Che Operator must already have all permissions
in these ClusterRoles to grant them.'
items:
type: string
@ -7065,6 +7066,17 @@ spec:
label.'
type: string
type: object
user:
description: User configuration.
properties:
clusterRoles:
description: Additional ClusterRoles assigned to the user.
The role must have `app.kubernetes.io/part-of=che.eclipse.org`
label.
items:
type: string
type: array
type: object
type: object
gitServices:
description: A configuration that allows users to work with remote

View File

@ -3957,12 +3957,13 @@ spec:
server.
properties:
clusterRoles:
description: 'ClusterRoles assigned to Che ServiceAccount.
The defaults roles are: - `<che-namespace>-cheworkspaces-namespaces-clusterrole`
- `<che-namespace>-cheworkspaces-clusterrole` - `<che-namespace>-cheworkspaces-devworkspace-clusterrole`
description: 'Additional ClusterRoles assigned to Che ServiceAccount.
Each role must have a `app.kubernetes.io/part-of=che.eclipse.org`
label. The defaults roles are: - `<che-namespace>-cheworkspaces-clusterrole`
- `<che-namespace>-cheworkspaces-namespaces-clusterrole`
- `<che-namespace>-cheworkspaces-devworkspace-clusterrole`
where the <che-namespace> is the namespace where the CheCluster
CRD is created. Each role must have a `app.kubernetes.io/part-of=che.eclipse.org`
label. The Che Operator must already have all permissions
CR is created. The Che Operator must already have all permissions
in these ClusterRoles to grant them.'
items:
type: string
@ -7070,6 +7071,17 @@ spec:
label.'
type: string
type: object
user:
description: User configuration.
properties:
clusterRoles:
description: Additional ClusterRoles assigned to the user.
The role must have `app.kubernetes.io/part-of=che.eclipse.org`
label.
items:
type: string
type: array
type: object
type: object
gitServices:
description: A configuration that allows users to work with remote

View File

@ -3952,12 +3952,13 @@ spec:
server.
properties:
clusterRoles:
description: 'ClusterRoles assigned to Che ServiceAccount.
The defaults roles are: - `<che-namespace>-cheworkspaces-namespaces-clusterrole`
- `<che-namespace>-cheworkspaces-clusterrole` - `<che-namespace>-cheworkspaces-devworkspace-clusterrole`
description: 'Additional ClusterRoles assigned to Che ServiceAccount.
Each role must have a `app.kubernetes.io/part-of=che.eclipse.org`
label. The defaults roles are: - `<che-namespace>-cheworkspaces-clusterrole`
- `<che-namespace>-cheworkspaces-namespaces-clusterrole`
- `<che-namespace>-cheworkspaces-devworkspace-clusterrole`
where the <che-namespace> is the namespace where the CheCluster
CRD is created. Each role must have a `app.kubernetes.io/part-of=che.eclipse.org`
label. The Che Operator must already have all permissions
CR is created. The Che Operator must already have all permissions
in these ClusterRoles to grant them.'
items:
type: string
@ -7065,6 +7066,17 @@ spec:
label.'
type: string
type: object
user:
description: User configuration.
properties:
clusterRoles:
description: Additional ClusterRoles assigned to the user.
The role must have `app.kubernetes.io/part-of=che.eclipse.org`
label.
items:
type: string
type: array
type: object
type: object
gitServices:
description: A configuration that allows users to work with remote

View File

@ -3952,12 +3952,13 @@ spec:
server.
properties:
clusterRoles:
description: 'ClusterRoles assigned to Che ServiceAccount.
The defaults roles are: - `<che-namespace>-cheworkspaces-namespaces-clusterrole`
- `<che-namespace>-cheworkspaces-clusterrole` - `<che-namespace>-cheworkspaces-devworkspace-clusterrole`
description: 'Additional ClusterRoles assigned to Che ServiceAccount.
Each role must have a `app.kubernetes.io/part-of=che.eclipse.org`
label. The defaults roles are: - `<che-namespace>-cheworkspaces-clusterrole`
- `<che-namespace>-cheworkspaces-namespaces-clusterrole`
- `<che-namespace>-cheworkspaces-devworkspace-clusterrole`
where the <che-namespace> is the namespace where the CheCluster
CRD is created. Each role must have a `app.kubernetes.io/part-of=che.eclipse.org`
label. The Che Operator must already have all permissions
CR is created. The Che Operator must already have all permissions
in these ClusterRoles to grant them.'
items:
type: string
@ -7065,6 +7066,17 @@ spec:
label.'
type: string
type: object
user:
description: User configuration.
properties:
clusterRoles:
description: Additional ClusterRoles assigned to the user.
The role must have `app.kubernetes.io/part-of=che.eclipse.org`
label.
items:
type: string
type: array
type: object
type: object
gitServices:
description: A configuration that allows users to work with remote

View File

@ -123,6 +123,7 @@ const (
CheFlavor = "che"
CheEclipseOrg = "che.eclipse.org"
InstallOrUpdateFailed = "InstallOrUpdateFailed"
FinalizerSuffix = "finalizers.che.eclipse.org"
// DevWorkspace
DevWorkspaceServiceAccountName = "devworkspace-controller-serviceaccount"

View File

@ -37,9 +37,6 @@ var (
defaultSingleHostGatewayConfigSidecarImage string
defaultGatewayAuthenticationSidecarImage string
defaultGatewayAuthorizationSidecarImage string
defaultCheWorkspacePluginBrokerMetadataImage string
defaultCheWorkspacePluginBrokerArtifactsImage string
defaultCheServerSecureExposerJwtProxyImage string
defaultConsoleLinkName string
defaultConsoleLinkDisplayName string
defaultConsoleLinkSection string
@ -160,30 +157,6 @@ func GetDevfileRegistryImage(checluster interface{}) string {
return PatchDefaultImageName(checluster, defaultDevfileRegistryImage)
}
func GetCheWorkspacePluginBrokerMetadataImage(checluster interface{}) string {
if !initialized {
logrus.Fatalf("Operator defaults are not initialized.")
}
return PatchDefaultImageName(checluster, defaultCheWorkspacePluginBrokerMetadataImage)
}
func GetCheWorkspacePluginBrokerArtifactsImage(checluster interface{}) string {
if !initialized {
logrus.Fatalf("Operator defaults are not initialized.")
}
return PatchDefaultImageName(checluster, defaultCheWorkspacePluginBrokerArtifactsImage)
}
func GetCheServerSecureExposerJwtProxyImage(checluster interface{}) string {
if !initialized {
logrus.Fatalf("Operator defaults are not initialized.")
}
return PatchDefaultImageName(checluster, defaultCheServerSecureExposerJwtProxyImage)
}
func GetGatewayImage(checluster interface{}) string {
if !initialized {
logrus.Fatalf("Operator defaults are not initialized.")

View File

@ -212,6 +212,12 @@ func CloneMap(m map[string]string) map[string]string {
return result
}
func AddMap(a map[string]string, b map[string]string) {
for k, v := range b {
a[k] = v
}
}
// Converts label map into plain string
func FormatLabels(m map[string]string) string {
if len(m) == 0 {

View File

@ -12,8 +12,6 @@
package deploy
import (
"strings"
"github.com/eclipse-che/che-operator/pkg/common/chetypes"
"github.com/eclipse-che/che-operator/pkg/common/constants"
defaults "github.com/eclipse-che/che-operator/pkg/common/operator-defaults"
@ -21,7 +19,6 @@ import (
"github.com/google/go-cmp/cmp/cmpopts"
rbac "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
)
var ClusterRoleBindingDiffOpts = cmp.Options{
@ -38,39 +35,6 @@ func SyncClusterRoleBindingToCluster(
return Sync(deployContext, crbSpec, ClusterRoleBindingDiffOpts)
}
func SyncClusterRoleBindingAndAddFinalizerToCluster(
deployContext *chetypes.DeployContext,
name string,
serviceAccountName string,
clusterRoleName string) (bool, error) {
finalizer := GetFinalizerName(strings.ToLower(name) + ".crb")
crbSpec := getClusterRoleBindingSpec(deployContext, name, serviceAccountName, deployContext.CheCluster.Namespace, clusterRoleName)
return SyncAndAddFinalizer(deployContext, crbSpec, ClusterRoleBindingDiffOpts, finalizer)
}
func ReconcileClusterRoleBindingFinalizer(deployContext *chetypes.DeployContext, name string) error {
if deployContext.CheCluster.DeletionTimestamp.IsZero() {
return nil
}
finalizer := GetFinalizerName(strings.ToLower(name) + ".crb")
return DeleteObjectWithFinalizer(deployContext, types.NamespacedName{Name: name}, &rbac.ClusterRoleBinding{}, finalizer)
}
func GetLegacyUniqueClusterRoleBindingName(deployContext *chetypes.DeployContext, serviceAccount string, clusterRole string) string {
return deployContext.CheCluster.Namespace + "-" + serviceAccount + "-" + clusterRole
}
func ReconcileLegacyClusterRoleBindingFinalizer(deployContext *chetypes.DeployContext, name string) error {
if deployContext.CheCluster.DeletionTimestamp.IsZero() {
return nil
}
finalizer := strings.ToLower(name) + ".clusterrolebinding.finalizers.che.eclipse.org"
return DeleteObjectWithFinalizer(deployContext, types.NamespacedName{Name: name}, &rbac.ClusterRoleBinding{}, finalizer)
}
func getClusterRoleBindingSpec(
deployContext *chetypes.DeployContext,
name string,

View File

@ -13,11 +13,9 @@ package deploy
import (
"context"
"time"
chev2 "github.com/eclipse-che/che-operator/api/v2"
"github.com/eclipse-che/che-operator/pkg/common/chetypes"
"github.com/eclipse-che/che-operator/pkg/common/utils"
rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
@ -71,63 +69,3 @@ func TestSyncClusterRoleBindingToCluster(t *testing.T) {
t.Fatalf("Failed to sync crb: %v", err)
}
}
func TestSyncClusterRoleBindingAndAddFinalizerToCluster(t *testing.T) {
chev2.SchemeBuilder.AddToScheme(scheme.Scheme)
rbacv1.SchemeBuilder.AddToScheme(scheme.Scheme)
cli := fake.NewFakeClientWithScheme(scheme.Scheme)
deployContext := &chetypes.DeployContext{
CheCluster: &chev2.CheCluster{
ObjectMeta: metav1.ObjectMeta{
Namespace: "eclipse-che",
Name: "eclipse-che",
},
},
ClusterAPI: chetypes.ClusterAPI{
Client: cli,
NonCachingClient: cli,
Scheme: scheme.Scheme,
},
}
cli.Create(context.TODO(), deployContext.CheCluster)
done, err := SyncClusterRoleBindingAndAddFinalizerToCluster(deployContext, "test", "sa", "clusterrole-1")
if !done || err != nil {
t.Fatalf("Failed to sync crb: %v", err)
}
if !utils.Contains(deployContext.CheCluster.Finalizers, "test.crb.finalizers.che.eclipse.org") {
t.Fatalf("Failed to add finalizer")
}
// don't expect any deletion
err = ReconcileClusterRoleBindingFinalizer(deployContext, "test")
if err != nil {
t.Fatalf("Failed to reconcile finalizer: %v", err)
}
actual := &rbacv1.ClusterRoleBinding{}
err = cli.Get(context.TODO(), types.NamespacedName{Name: "test"}, actual)
if err != nil {
t.Fatalf("CRD shouldn't be deleted: %v", err)
}
if !utils.Contains(deployContext.CheCluster.Finalizers, "test.crb.finalizers.che.eclipse.org") {
t.Fatalf("Finalizer shouldn't be deleted")
}
// crb must be deleted as well as finalizer
deployContext.CheCluster.ObjectMeta.DeletionTimestamp = &metav1.Time{Time: time.Now()}
err = ReconcileClusterRoleBindingFinalizer(deployContext, "test")
if err != nil {
t.Fatalf("Failed to reconcile finalizer: %v", err)
}
actual = &rbacv1.ClusterRoleBinding{}
err = cli.Get(context.TODO(), types.NamespacedName{Name: "test"}, actual)
if err == nil {
t.Fatalf("Failed to remove crb: %v", err)
}
if utils.Contains(deployContext.CheCluster.Finalizers, "test.crb.finalizers.che.eclipse.org") {
t.Fatalf("Failed to remove finalizer")
}
}

View File

@ -21,6 +21,11 @@ import (
"sigs.k8s.io/controller-runtime/pkg/client"
)
func CleanUpAllFinalizers(ctx *chetypes.DeployContext) error {
ctx.CheCluster.Finalizers = []string{}
return ctx.ClusterAPI.Client.Update(context.TODO(), ctx.CheCluster)
}
func AppendFinalizer(deployContext *chetypes.DeployContext, finalizer string) error {
if err := ReloadCheClusterCR(deployContext); err != nil {
return err
@ -78,12 +83,3 @@ func DeleteObjectWithFinalizer(deployContext *chetypes.DeployContext, key client
return DeleteFinalizer(deployContext, finalizer)
}
func GetFinalizerName(prefix string) string {
finalizer := prefix + ".finalizers.che.eclipse.org"
diff := len(finalizer) - 63
if diff > 0 {
return finalizer[:len(finalizer)-diff]
}
return finalizer
}

View File

@ -99,13 +99,3 @@ func TestDeleteFinalizer(t *testing.T) {
t.Fatalf("Failed to delete finalizer: %v", err)
}
}
func TestGetFinalizerNameShouldReturnStringLess64Chars(t *testing.T) {
expected := "7890123456789012345678901234567891234567.finalizers.che.eclipse"
prefix := "7890123456789012345678901234567891234567"
actual := GetFinalizerName(prefix)
if expected != actual {
t.Fatalf("Incorrect finalizer name: %s", actual)
}
}

View File

@ -1,78 +0,0 @@
//
// Copyright (c) 2012-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 rbac
import (
"strings"
"github.com/eclipse-che/che-operator/pkg/common/chetypes"
"github.com/eclipse-che/che-operator/pkg/common/constants"
"github.com/eclipse-che/che-operator/pkg/deploy"
"github.com/sirupsen/logrus"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
type CheServerPermissionsReconciler struct {
deploy.Reconcilable
}
func NewCheServerPermissionsReconciler() *CheServerPermissionsReconciler {
return &CheServerPermissionsReconciler{}
}
func (c *CheServerPermissionsReconciler) Reconcile(ctx *chetypes.DeployContext) (reconcile.Result, bool, error) {
// Create service account "che" for che-server component.
// "che" is the one which token is used to create workspace objects.
// Notice: Also we have on more "che-workspace" SA used by plugins like exec, terminal, metrics with limited privileges.
done, err := deploy.SyncServiceAccountToCluster(ctx, constants.DefaultCheServiceAccountName)
if !done {
return reconcile.Result{Requeue: true}, false, err
}
for _, cheClusterRole := range ctx.CheCluster.Spec.Components.CheServer.ClusterRoles {
cheClusterRole := strings.TrimSpace(cheClusterRole)
if cheClusterRole != "" {
cheClusterRoleBindingName := cheClusterRole
done, err := deploy.SyncClusterRoleBindingAndAddFinalizerToCluster(ctx, cheClusterRoleBindingName, constants.DefaultCheServiceAccountName, cheClusterRole)
if !done {
return reconcile.Result{Requeue: true}, false, err
}
}
}
return reconcile.Result{}, true, err
}
func (c *CheServerPermissionsReconciler) Finalize(ctx *chetypes.DeployContext) bool {
done := true
for _, cheClusterRole := range ctx.CheCluster.Spec.Components.CheServer.ClusterRoles {
cheClusterRole := strings.TrimSpace(cheClusterRole)
if cheClusterRole != "" {
cheClusterRoleBindingName := cheClusterRole
if err := deploy.ReconcileClusterRoleBindingFinalizer(ctx, cheClusterRoleBindingName); err != nil {
done = false
logrus.Errorf("Error deleting finalizer: %v", err)
}
// Removes any legacy CRB https://github.com/eclipse/che/issues/19506
cheClusterRoleBindingName = deploy.GetLegacyUniqueClusterRoleBindingName(ctx, constants.DefaultCheServiceAccountName, cheClusterRole)
if err := deploy.ReconcileLegacyClusterRoleBindingFinalizer(ctx, cheClusterRoleBindingName); err != nil {
done = false
logrus.Errorf("Error deleting finalizer: %v", err)
}
}
}
return done
}

View File

@ -1,387 +0,0 @@
//
// 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 rbac
import (
"fmt"
"github.com/devfile/devworkspace-operator/pkg/infrastructure"
"github.com/eclipse-che/che-operator/pkg/common/chetypes"
"github.com/eclipse-che/che-operator/pkg/common/constants"
"github.com/eclipse-che/che-operator/pkg/deploy"
"github.com/sirupsen/logrus"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/types"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
const (
// CheNamespaceEditorClusterRoleNameTemplate - manage namespaces "cluster role" and "clusterrolebinding" template name
CheNamespaceEditorClusterRoleNameTemplate = "%s-cheworkspaces-namespaces-clusterrole"
// CheWorkspacesClusterRoleNameTemplate - manage workspaces "cluster role" and "clusterrolebinding" template name
CheWorkspacesClusterRoleNameTemplate = "%s-cheworkspaces-clusterrole"
// DevWorkspaceClusterRoleNameTemplate - manage DevWorkspace "cluster role" and "clusterrolebinding" template name
DevWorkspaceClusterRoleNameTemplate = "%s-cheworkspaces-devworkspace-clusterrole"
CheWorkspacesClusterPermissionsFinalizerName = "cheWorkspaces.clusterpermissions.finalizers.che.eclipse.org"
NamespacesEditorPermissionsFinalizerName = "namespaces-editor.permissions.finalizers.che.eclipse.org"
DevWorkspacePermissionsFinalizerName = "devWorkspace.permissions.finalizers.che.eclipse.org"
)
type WorkspacePermissionsReconciler struct {
deploy.Reconcilable
}
func NewWorkspacePermissionsReconciler() *WorkspacePermissionsReconciler {
return &WorkspacePermissionsReconciler{}
}
func (wp *WorkspacePermissionsReconciler) Reconcile(ctx *chetypes.DeployContext) (reconcile.Result, bool, error) {
done, err := wp.delegateWorkspacePermissionsInTheDifferNamespaceThanChe(ctx)
if !done {
return reconcile.Result{Requeue: true}, false, err
}
done, err = wp.delegateNamespaceEditorPermissions(ctx)
if !done {
return reconcile.Result{Requeue: true}, false, err
}
done, err = wp.delegateDevWorkspacePermissions(ctx)
if !done {
return reconcile.Result{Requeue: true}, false, err
}
return reconcile.Result{}, true, nil
}
func (wp *WorkspacePermissionsReconciler) Finalize(ctx *chetypes.DeployContext) bool {
done := true
if completed := wp.removeNamespaceEditorPermissions(ctx); !completed {
done = false
}
if completed := wp.removeDevWorkspacePermissions(ctx); !completed {
done = false
}
if completed := wp.removeWorkspacePermissionsInTheDifferNamespaceThanChe(ctx); !completed {
done = false
}
return done
}
// Create cluster roles and cluster role bindings for "che" service account.
// che-server uses "che" service account for creation new workspaces and workspace components.
// Operator will create two cluster roles:
// - "<workspace-namespace/project-name>-cheworkspaces-namespaces-clusterrole" - cluster role to manage namespace(for Kubernetes platform)
// or project(for Openshift platform) for new workspace.
// - "<workspace-namespace/project-name>-cheworkspaces-clusterrole" - cluster role to create and manage k8s objects required for
// workspace components.
// Notice: After permission delegation che-server will create service account "che-workspace" ITSELF with
// "exec" and "view" roles for each new workspace.
func (wp *WorkspacePermissionsReconciler) delegateWorkspacePermissionsInTheDifferNamespaceThanChe(deployContext *chetypes.DeployContext) (bool, error) {
сheWorkspacesClusterRoleName := fmt.Sprintf(CheWorkspacesClusterRoleNameTemplate, deployContext.CheCluster.Namespace)
сheWorkspacesClusterRoleBindingName := сheWorkspacesClusterRoleName
// Create clusterrole +kubebuilder:storageversion"<workspace-namespace/project-name>-cheworkspaces-namespaces-clusterrole" to create k8s components for Che workspaces.
done, err := deploy.SyncClusterRoleToCluster(deployContext, сheWorkspacesClusterRoleName, wp.getWorkspacesPolicies())
if !done {
return false, err
}
done, err = deploy.SyncClusterRoleBindingToCluster(deployContext, сheWorkspacesClusterRoleBindingName, constants.DefaultCheServiceAccountName, сheWorkspacesClusterRoleName)
if !done {
return false, err
}
err = deploy.AppendFinalizer(deployContext, CheWorkspacesClusterPermissionsFinalizerName)
return err == nil, err
}
func (wp *WorkspacePermissionsReconciler) removeWorkspacePermissionsInTheDifferNamespaceThanChe(deployContext *chetypes.DeployContext) bool {
done := true
cheWorkspacesClusterRoleName := fmt.Sprintf(CheWorkspacesClusterRoleNameTemplate, deployContext.CheCluster.Namespace)
cheWorkspacesClusterRoleBindingName := cheWorkspacesClusterRoleName
if _, err := deploy.Delete(deployContext, types.NamespacedName{Name: cheWorkspacesClusterRoleName}, &rbacv1.ClusterRole{}); err != nil {
done = false
logrus.Errorf("Failed to delete Che Workspaces ClusterRole '%s', cause: %v", cheWorkspacesClusterRoleName, err)
}
if _, err := deploy.Delete(deployContext, types.NamespacedName{Name: cheWorkspacesClusterRoleBindingName}, &rbacv1.ClusterRoleBinding{}); err != nil {
done = false
logrus.Errorf("Failed to delete Che Workspace ClusterRoleBinding '%s', cause: %v", cheWorkspacesClusterRoleBindingName, err)
}
if err := deploy.DeleteFinalizer(deployContext, CheWorkspacesClusterPermissionsFinalizerName); err != nil {
done = false
logrus.Errorf("Error deleting finalizer: %v", err)
}
return done
}
func (wp *WorkspacePermissionsReconciler) delegateNamespaceEditorPermissions(deployContext *chetypes.DeployContext) (bool, error) {
сheNamespaceEditorClusterRoleName := fmt.Sprintf(CheNamespaceEditorClusterRoleNameTemplate, deployContext.CheCluster.Namespace)
сheNamespaceEditorClusterRoleBindingName := сheNamespaceEditorClusterRoleName
// Create clusterrole "<workspace-namespace/project-name>-clusterrole-manage-namespaces" to manage namespace/projects for Che workspaces.
done, err := deploy.SyncClusterRoleToCluster(deployContext, сheNamespaceEditorClusterRoleName, wp.getNamespaceEditorPolicies())
if !done {
return false, err
}
done, err = deploy.SyncClusterRoleBindingToCluster(deployContext, сheNamespaceEditorClusterRoleBindingName, constants.DefaultCheServiceAccountName, сheNamespaceEditorClusterRoleName)
if !done {
return false, err
}
err = deploy.AppendFinalizer(deployContext, NamespacesEditorPermissionsFinalizerName)
return err == nil, err
}
func (wp *WorkspacePermissionsReconciler) removeNamespaceEditorPermissions(deployContext *chetypes.DeployContext) bool {
done := true
cheNamespaceEditorClusterRoleName := fmt.Sprintf(CheNamespaceEditorClusterRoleNameTemplate, deployContext.CheCluster.Namespace)
if _, err := deploy.Delete(deployContext, types.NamespacedName{Name: cheNamespaceEditorClusterRoleName}, &rbacv1.ClusterRole{}); err != nil {
done = false
logrus.Errorf("Failed to delete Editor ClusterRole '%s', cause: %v", cheNamespaceEditorClusterRoleName, err)
}
if _, err := deploy.Delete(deployContext, types.NamespacedName{Name: cheNamespaceEditorClusterRoleName}, &rbacv1.ClusterRoleBinding{}); err != nil {
done = false
logrus.Errorf("Failed to delete Editor ClusterRoleBinding '%s', cause: %v", cheNamespaceEditorClusterRoleName, err)
}
if err := deploy.DeleteFinalizer(deployContext, NamespacesEditorPermissionsFinalizerName); err != nil {
done = false
logrus.Errorf("Error deleting finalizer: %v", err)
}
return done
}
func (wp *WorkspacePermissionsReconciler) delegateDevWorkspacePermissions(deployContext *chetypes.DeployContext) (bool, error) {
devWorkspaceClusterRoleName := fmt.Sprintf(DevWorkspaceClusterRoleNameTemplate, deployContext.CheCluster.Namespace)
devWorkspaceClusterRoleBindingName := devWorkspaceClusterRoleName
done, err := deploy.SyncClusterRoleToCluster(deployContext, devWorkspaceClusterRoleName, wp.getDevWorkspacePolicies())
if !done {
return false, err
}
done, err = deploy.SyncClusterRoleBindingToCluster(deployContext, devWorkspaceClusterRoleBindingName, constants.DefaultCheServiceAccountName, devWorkspaceClusterRoleName)
if !done {
return false, err
}
err = deploy.AppendFinalizer(deployContext, DevWorkspacePermissionsFinalizerName)
return err == nil, err
}
func (wp *WorkspacePermissionsReconciler) removeDevWorkspacePermissions(deployContext *chetypes.DeployContext) bool {
done := true
devWorkspaceClusterRoleName := fmt.Sprintf(DevWorkspaceClusterRoleNameTemplate, deployContext.CheCluster.Namespace)
devWorkspaceClusterRoleBindingName := devWorkspaceClusterRoleName
if _, err := deploy.Delete(deployContext, types.NamespacedName{Name: devWorkspaceClusterRoleName}, &rbacv1.ClusterRole{}); err != nil {
done = false
logrus.Errorf("Failed to delete DevWorkspace ClusterRole '%s', cause: %v", devWorkspaceClusterRoleName, err)
}
if _, err := deploy.Delete(deployContext, types.NamespacedName{Name: devWorkspaceClusterRoleBindingName}, &rbacv1.ClusterRoleBinding{}); err != nil {
done = false
logrus.Errorf("Failed to delete DevWorkspace ClusterRoleBinding '%s', cause: %v", devWorkspaceClusterRoleName, err)
}
if err := deploy.DeleteFinalizer(deployContext, DevWorkspacePermissionsFinalizerName); err != nil {
done = false
logrus.Errorf("Error deleting finalizer: %v", err)
}
return done
}
func (wp *WorkspacePermissionsReconciler) getDevWorkspacePolicies() []rbacv1.PolicyRule {
k8sPolicies := []rbacv1.PolicyRule{
{
APIGroups: []string{"workspace.devfile.io"},
Resources: []string{"devworkspaces", "devworkspacetemplates"},
Verbs: []string{"get", "create", "delete", "list", "update", "patch", "watch"},
},
}
return k8sPolicies
}
func (wp *WorkspacePermissionsReconciler) getNamespaceEditorPolicies() []rbacv1.PolicyRule {
k8sPolicies := []rbacv1.PolicyRule{
{
APIGroups: []string{""},
Resources: []string{"namespaces"},
Verbs: []string{"get", "create", "update", "list"},
},
}
openshiftPolicies := []rbacv1.PolicyRule{
{
APIGroups: []string{"project.openshift.io"},
Resources: []string{"projectrequests"},
Verbs: []string{"create", "update"},
},
{
APIGroups: []string{"project.openshift.io"},
Resources: []string{"projects"},
Verbs: []string{"get", "list"},
},
}
if infrastructure.IsOpenShift() {
return append(k8sPolicies, openshiftPolicies...)
}
return k8sPolicies
}
func (c *WorkspacePermissionsReconciler) getWorkspacesPolicies() []rbacv1.PolicyRule {
k8sPolicies := []rbacv1.PolicyRule{
{
APIGroups: []string{""},
Resources: []string{"serviceaccounts"},
Verbs: []string{"get", "watch", "create"},
},
{
APIGroups: []string{""},
Resources: []string{"pods/exec"},
Verbs: []string{"get", "create"},
},
{
APIGroups: []string{""},
Resources: []string{"pods/log"},
Verbs: []string{"get", "list", "watch"},
},
{
APIGroups: []string{""},
Resources: []string{"secrets"},
Verbs: []string{"get", "list", "create", "update", "patch", "delete"},
},
{
APIGroups: []string{""},
Resources: []string{"persistentvolumeclaims"},
Verbs: []string{"get", "list", "watch", "create", "delete"},
},
{
APIGroups: []string{""},
Resources: []string{"pods"},
Verbs: []string{"get", "list", "watch", "create", "delete"},
},
{
APIGroups: []string{""},
Resources: []string{"services"},
Verbs: []string{"get", "list", "create", "delete"},
},
{
APIGroups: []string{""},
Resources: []string{"configmaps"},
Verbs: []string{"get", "list", "create", "update", "patch", "delete"},
},
{
APIGroups: []string{""},
Resources: []string{"events"},
Verbs: []string{"watch"},
},
{
APIGroups: []string{"apps"},
Resources: []string{"secrets"},
Verbs: []string{"list"},
},
{
APIGroups: []string{"apps"},
Resources: []string{"deployments"},
Verbs: []string{"get", "list", "watch", "create", "patch", "delete"},
},
{
APIGroups: []string{"apps"},
Resources: []string{"replicasets"},
Verbs: []string{"get", "list", "patch", "delete"},
},
{
APIGroups: []string{"extensions"},
Resources: []string{"ingresses"},
Verbs: []string{"get", "list", "watch", "create", "delete"},
},
{
APIGroups: []string{"networking.k8s.io"},
Resources: []string{"ingresses"},
Verbs: []string{"get", "list", "watch", "create", "delete"},
},
{
APIGroups: []string{"rbac.authorization.k8s.io"},
Resources: []string{"roles"},
Verbs: []string{"get", "create", "update"},
},
{
APIGroups: []string{"rbac.authorization.k8s.io"},
Resources: []string{"rolebindings"},
Verbs: []string{"get", "create", "update"},
},
{
APIGroups: []string{"metrics.k8s.io"},
Resources: []string{"pods", "nodes"},
Verbs: []string{"get", "list", "watch"},
},
{
APIGroups: []string{""},
Resources: []string{"namespaces"},
Verbs: []string{"get", "list"},
},
{
APIGroups: []string{""},
Resources: []string{"events"},
Verbs: []string{"watch", "list"},
},
}
openshiftPolicies := []rbacv1.PolicyRule{
{
APIGroups: []string{"route.openshift.io"},
Resources: []string{"routes"},
Verbs: []string{"get", "list", "create", "delete"},
},
{
APIGroups: []string{"authorization.openshift.io"},
Resources: []string{"roles"},
Verbs: []string{"get", "create", "update"},
},
{
APIGroups: []string{"authorization.openshift.io"},
Resources: []string{"rolebindings"},
Verbs: []string{"get", "create", "update"},
},
{
APIGroups: []string{"project.openshift.io"},
Resources: []string{"projects"},
Verbs: []string{"get"},
},
}
if infrastructure.IsOpenShift() {
return append(k8sPolicies, openshiftPolicies...)
}
return k8sPolicies
}

View File

@ -1,102 +0,0 @@
//
// 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 rbac
import (
"testing"
"github.com/devfile/devworkspace-operator/pkg/infrastructure"
chev2 "github.com/eclipse-che/che-operator/api/v2"
"github.com/eclipse-che/che-operator/pkg/common/test"
"github.com/eclipse-che/che-operator/pkg/common/utils"
"github.com/stretchr/testify/assert"
rbac "k8s.io/api/rbac/v1"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
)
func TestReconcileWorkspacePermissions(t *testing.T) {
infrastructure.InitializeForTesting(infrastructure.OpenShiftv4)
type testCase struct {
name string
initObjects []runtime.Object
checluster *chev2.CheCluster
}
testCases := []testCase{
{
name: "che-operator should delegate permission for workspaces in differ namespace than Che. WorkspaceNamespaceDefault = 'some-test-namespace'",
initObjects: []runtime.Object{},
checluster: &chev2.CheCluster{
ObjectMeta: v1.ObjectMeta{
Name: "eclipse-che",
Namespace: "eclipse-che",
},
Spec: chev2.CheClusterSpec{
DevEnvironments: chev2.CheClusterDevEnvironments{
DefaultNamespace: chev2.DefaultNamespace{
Template: "some-test-namespace",
},
},
},
},
},
{
name: "che-operator should delegate permission for workspaces in differ namespace than Che. Property CHE_INFRA_KUBERNETES_NAMESPACE_DEFAULT = 'some-test-namespace'",
initObjects: []runtime.Object{},
checluster: &chev2.CheCluster{
ObjectMeta: v1.ObjectMeta{
Name: "eclipse-che",
Namespace: "eclipse-che",
},
Spec: chev2.CheClusterSpec{
Components: chev2.CheClusterComponents{
CheServer: chev2.CheServer{
ExtraProperties: map[string]string{
"CHE_INFRA_KUBERNETES_NAMESPACE_DEFAULT": "some-test-namespace",
},
},
},
},
},
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
ctx := test.GetDeployContext(testCase.checluster, testCase.initObjects)
wp := NewWorkspacePermissionsReconciler()
_, done, err := wp.Reconcile(ctx)
assert.Nil(t, err)
assert.True(t, done)
assert.True(t, utils.Contains(ctx.CheCluster.Finalizers, CheWorkspacesClusterPermissionsFinalizerName))
assert.True(t, utils.Contains(ctx.CheCluster.Finalizers, NamespacesEditorPermissionsFinalizerName))
assert.True(t, utils.Contains(ctx.CheCluster.Finalizers, DevWorkspacePermissionsFinalizerName))
name := "eclipse-che-cheworkspaces-clusterrole"
assert.True(t, test.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: name}, &rbac.ClusterRole{}))
assert.True(t, test.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: name}, &rbac.ClusterRoleBinding{}))
name = "eclipse-che-cheworkspaces-namespaces-clusterrole"
assert.True(t, test.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: name}, &rbac.ClusterRole{}))
assert.True(t, test.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: name}, &rbac.ClusterRoleBinding{}))
name = "eclipse-che-cheworkspaces-devworkspace-clusterrole"
assert.True(t, test.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: name}, &rbac.ClusterRole{}))
assert.True(t, test.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: name}, &rbac.ClusterRoleBinding{}))
})
}
}

View File

@ -20,6 +20,8 @@ import (
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
const Finalizer = "cluster-resources." + constants.FinalizerSuffix
type Reconcilable interface {
// Reconcile object.
Reconcile(ctx *chetypes.DeployContext) (result reconcile.Result, done bool, err error)
@ -46,6 +48,10 @@ func (manager *ReconcileManager) RegisterReconciler(reconciler Reconcilable) {
// Reconcile all objects in a order they have been added
// If reconciliation failed then CheCluster status will be updated accordingly.
func (manager *ReconcileManager) ReconcileAll(ctx *chetypes.DeployContext) (reconcile.Result, bool, error) {
if err := AppendFinalizer(ctx, Finalizer); err != nil {
return reconcile.Result{}, false, err
}
for _, reconciler := range manager.reconcilers {
result, done, err := reconciler.Reconcile(ctx)
if err != nil {
@ -75,13 +81,18 @@ func (manager *ReconcileManager) FinalizeAll(ctx *chetypes.DeployContext) (done
for _, reconciler := range manager.reconcilers {
if completed := reconciler.Finalize(ctx); !completed {
reconcilerName := GetObjectType(reconciler)
errMsg := fmt.Sprintf("Finalization failed for reconciler: %s", reconcilerName)
ctx.CheCluster.Status.Message = errMsg
_ = UpdateCheCRStatus(ctx, "Message", errMsg)
ctx.CheCluster.Status.Message = fmt.Sprintf("Finalization failed for reconciler: %s", reconcilerName)
_ = UpdateCheCRStatus(ctx, "Message", ctx.CheCluster.Status.Message)
done = false
}
}
if done {
if err := CleanUpAllFinalizers(ctx); err != nil {
return false
}
}
return done
}

View File

@ -24,31 +24,43 @@ import (
type TestReconcilable struct {
shouldFailReconcileOnce bool
alreadyFailed bool
shouldFailFinalizerOnce bool
alreadyFailedReconcile bool
alreadyFailedFinalizer bool
}
func NewTestReconcilable(shouldFailReconcileOnce bool) *TestReconcilable {
return &TestReconcilable{shouldFailReconcileOnce, false}
func NewTestReconcilable(shouldFailReconcileOnce bool, shouldFailFinalizerOnce bool) *TestReconcilable {
return &TestReconcilable{
shouldFailReconcileOnce,
shouldFailFinalizerOnce,
false,
false}
}
func (tr *TestReconcilable) Reconcile(ctx *chetypes.DeployContext) (reconcile.Result, bool, error) {
// Fails on first invocation passes on others
if !tr.alreadyFailed && tr.shouldFailReconcileOnce {
tr.alreadyFailed = true
return reconcile.Result{}, false, fmt.Errorf("Reconcile error")
if !tr.alreadyFailedReconcile && tr.shouldFailReconcileOnce {
tr.alreadyFailedReconcile = true
return reconcile.Result{}, false, fmt.Errorf("reconcile error")
} else {
return reconcile.Result{}, true, nil
}
}
func (tr *TestReconcilable) Finalize(ctx *chetypes.DeployContext) bool {
return true
// Fails on first invocation passes on others
if !tr.alreadyFailedFinalizer && tr.shouldFailFinalizerOnce {
tr.alreadyFailedFinalizer = true
return false
} else {
return true
}
}
func TestShouldUpdateAndCleanStatus(t *testing.T) {
deployContext := test.GetDeployContext(nil, []runtime.Object{})
tr := NewTestReconcilable(true)
tr := NewTestReconcilable(true, false)
rm := NewReconcileManager()
rm.RegisterReconciler(tr)
@ -58,7 +70,7 @@ func TestShouldUpdateAndCleanStatus(t *testing.T) {
assert.False(t, done)
assert.NotNil(t, err)
assert.NotEmpty(t, deployContext.CheCluster.Status.Reason)
assert.Equal(t, "Reconciler failed deploy.TestReconcilable, cause: Reconcile error", deployContext.CheCluster.Status.Message)
assert.Equal(t, "Reconciler failed deploy.TestReconcilable, cause: reconcile error", deployContext.CheCluster.Status.Message)
assert.Equal(t, tr, rm.failedReconciler)
_, done, err = rm.ReconcileAll(deployContext)
@ -69,3 +81,39 @@ func TestShouldUpdateAndCleanStatus(t *testing.T) {
assert.Empty(t, deployContext.CheCluster.Status.Message)
assert.Nil(t, rm.failedReconciler)
}
func TestShouldCleanUpAllFinalizers(t *testing.T) {
ctx := test.GetDeployContext(nil, []runtime.Object{})
rm := NewReconcileManager()
rm.RegisterReconciler(NewTestReconcilable(false, false))
_, done, err := rm.ReconcileAll(ctx)
assert.True(t, done)
assert.NoError(t, err)
assert.Equal(t, 1, len(ctx.CheCluster.Finalizers))
done = rm.FinalizeAll(ctx)
assert.True(t, done)
assert.Empty(t, ctx.CheCluster.Finalizers)
}
func TestShouldNotCleanUpAllFinalizersIfFailure(t *testing.T) {
ctx := test.GetDeployContext(nil, []runtime.Object{})
rm := NewReconcileManager()
rm.RegisterReconciler(NewTestReconcilable(false, true))
_, done, err := rm.ReconcileAll(ctx)
assert.True(t, done)
assert.NoError(t, err)
assert.Equal(t, 1, len(ctx.CheCluster.Finalizers))
done = rm.FinalizeAll(ctx)
assert.False(t, done)
assert.Equal(t, 1, len(ctx.CheCluster.Finalizers))
done = rm.FinalizeAll(ctx)
assert.True(t, done)
assert.Empty(t, ctx.CheCluster.Finalizers)
}

301
pkg/deploy/server/rbac.go Normal file
View File

@ -0,0 +1,301 @@
//
// 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 server
import (
"fmt"
"strings"
"github.com/devfile/devworkspace-operator/pkg/infrastructure"
util "github.com/eclipse-che/che-operator/pkg/common/utils"
"github.com/sirupsen/logrus"
"github.com/eclipse-che/che-operator/pkg/common/chetypes"
"github.com/eclipse-che/che-operator/pkg/common/constants"
"github.com/eclipse-che/che-operator/pkg/deploy"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/types"
)
const (
commonPermissionsTemplateName = "%s-cheworkspaces-clusterrole"
namespacePermissionsTemplateName = "%s-cheworkspaces-namespaces-clusterrole"
devWorkspacePermissionsTemplateName = "%s-cheworkspaces-devworkspace-clusterrole"
)
// Create ClusterRole and ClusterRoleBinding for "che" service account.
// che-server uses "che" service account for creation RBAC for a user in his namespace.
func (s *CheServerReconciler) syncPermissions(ctx *chetypes.DeployContext) (bool, error) {
policies := map[string][]rbacv1.PolicyRule{
fmt.Sprintf(commonPermissionsTemplateName, ctx.CheCluster.Namespace): s.getCommonPolicies(),
fmt.Sprintf(namespacePermissionsTemplateName, ctx.CheCluster.Namespace): s.getNamespaceEditorPolicies(),
fmt.Sprintf(devWorkspacePermissionsTemplateName, ctx.CheCluster.Namespace): s.getDevWorkspacePolicies(),
}
for name, policy := range policies {
if done, err := deploy.SyncClusterRoleToCluster(ctx, name, policy); !done {
return false, err
}
if done, err := deploy.SyncClusterRoleBindingToCluster(ctx, name, constants.DefaultCheServiceAccountName, name); !done {
return false, err
}
}
for _, cheClusterRole := range ctx.CheCluster.Spec.Components.CheServer.ClusterRoles {
cheClusterRole := strings.TrimSpace(cheClusterRole)
if cheClusterRole != "" {
if done, err := deploy.SyncClusterRoleBindingToCluster(ctx, cheClusterRole, constants.DefaultCheServiceAccountName, cheClusterRole); !done {
return false, err
}
finalizer := s.getCRBFinalizerName(cheClusterRole)
if err := deploy.AppendFinalizer(ctx, finalizer); err != nil {
return false, err
}
}
}
// Delete abandoned CRBs
for _, finalizer := range ctx.CheCluster.Finalizers {
if strings.HasSuffix(finalizer, cheCRBFinalizerSuffix) {
cheClusterRole := strings.TrimSuffix(finalizer, cheCRBFinalizerSuffix)
if !util.Contains(ctx.CheCluster.Spec.Components.CheServer.ClusterRoles, cheClusterRole) {
if done, err := deploy.Delete(ctx, types.NamespacedName{Name: cheClusterRole}, &rbacv1.ClusterRoleBinding{}); !done {
return false, err
}
if err := deploy.DeleteFinalizer(ctx, finalizer); err != nil {
return false, err
}
}
}
}
return true, nil
}
func (s *CheServerReconciler) deletePermissions(ctx *chetypes.DeployContext) bool {
names := []string{
fmt.Sprintf(commonPermissionsTemplateName, ctx.CheCluster.Namespace),
fmt.Sprintf(namespacePermissionsTemplateName, ctx.CheCluster.Namespace),
fmt.Sprintf(devWorkspacePermissionsTemplateName, ctx.CheCluster.Namespace),
}
done := true
for _, name := range names {
if _, err := deploy.Delete(ctx, types.NamespacedName{Name: name}, &rbacv1.ClusterRole{}); err != nil {
done = false
logrus.Errorf("Failed to delete ClusterRole '%s', cause: %v", name, err)
}
if _, err := deploy.Delete(ctx, types.NamespacedName{Name: name}, &rbacv1.ClusterRoleBinding{}); err != nil {
done = false
logrus.Errorf("Failed to delete ClusterRoleBinding '%s', cause: %v", name, err)
}
}
for _, name := range ctx.CheCluster.Spec.Components.CheServer.ClusterRoles {
name := strings.TrimSpace(name)
if name != "" {
if _, err := deploy.Delete(ctx, types.NamespacedName{Name: name}, &rbacv1.ClusterRoleBinding{}); err != nil {
done = false
logrus.Errorf("Failed to delete ClusterRoleBinding '%s', cause: %v", name, err)
}
// Removes any legacy CRB https://github.com/eclipse/che/issues/19506
legacyName := ctx.CheCluster.Namespace + "-" + constants.DefaultCheServiceAccountName + "-" + name
if _, err := deploy.Delete(ctx, types.NamespacedName{Name: legacyName}, &rbacv1.ClusterRoleBinding{}); err != nil {
done = false
logrus.Errorf("Failed to delete ClusterRoleBinding '%s', cause: %v", legacyName, err)
}
}
}
return done
}
func (s *CheServerReconciler) getDevWorkspacePolicies() []rbacv1.PolicyRule {
k8sPolicies := []rbacv1.PolicyRule{
{
APIGroups: []string{"workspace.devfile.io"},
Resources: []string{"devworkspaces", "devworkspacetemplates"},
Verbs: []string{"get", "create", "delete", "list", "update", "patch", "watch"},
},
}
return k8sPolicies
}
func (s *CheServerReconciler) getNamespaceEditorPolicies() []rbacv1.PolicyRule {
k8sPolicies := []rbacv1.PolicyRule{
{
APIGroups: []string{""},
Resources: []string{"namespaces"},
Verbs: []string{"get", "create", "update", "list"},
},
}
openshiftPolicies := []rbacv1.PolicyRule{
{
APIGroups: []string{"project.openshift.io"},
Resources: []string{"projectrequests"},
Verbs: []string{"create", "update"},
},
{
APIGroups: []string{"project.openshift.io"},
Resources: []string{"projects"},
Verbs: []string{"get", "list"},
},
}
if infrastructure.IsOpenShift() {
return append(k8sPolicies, openshiftPolicies...)
}
return k8sPolicies
}
func (s *CheServerReconciler) getCommonPolicies() []rbacv1.PolicyRule {
k8sPolicies := []rbacv1.PolicyRule{
{
APIGroups: []string{""},
Resources: []string{"serviceaccounts"},
Verbs: []string{"get", "watch", "create"},
},
{
APIGroups: []string{""},
Resources: []string{"pods/exec"},
Verbs: []string{"get", "create"},
},
{
APIGroups: []string{""},
Resources: []string{"pods/log"},
Verbs: []string{"get", "list", "watch"},
},
{
APIGroups: []string{""},
Resources: []string{"secrets"},
Verbs: []string{"get", "list", "create", "update", "patch", "delete"},
},
{
APIGroups: []string{""},
Resources: []string{"persistentvolumeclaims"},
Verbs: []string{"get", "list", "watch", "create", "delete"},
},
{
APIGroups: []string{""},
Resources: []string{"pods"},
Verbs: []string{"get", "list", "watch", "create", "delete"},
},
{
APIGroups: []string{""},
Resources: []string{"services"},
Verbs: []string{"get", "list", "create", "delete"},
},
{
APIGroups: []string{""},
Resources: []string{"configmaps"},
Verbs: []string{"get", "list", "create", "update", "patch", "delete"},
},
{
APIGroups: []string{""},
Resources: []string{"events"},
Verbs: []string{"watch"},
},
{
APIGroups: []string{"apps"},
Resources: []string{"secrets"},
Verbs: []string{"list"},
},
{
APIGroups: []string{"apps"},
Resources: []string{"deployments"},
Verbs: []string{"get", "list", "watch", "create", "patch", "delete"},
},
{
APIGroups: []string{"apps"},
Resources: []string{"replicasets"},
Verbs: []string{"get", "list", "patch", "delete"},
},
{
APIGroups: []string{"extensions"},
Resources: []string{"ingresses"},
Verbs: []string{"get", "list", "watch", "create", "delete"},
},
{
APIGroups: []string{"networking.k8s.io"},
Resources: []string{"ingresses"},
Verbs: []string{"get", "list", "watch", "create", "delete"},
},
{
APIGroups: []string{"rbac.authorization.k8s.io"},
Resources: []string{"roles"},
Verbs: []string{"get", "create", "update"},
},
{
APIGroups: []string{"rbac.authorization.k8s.io"},
Resources: []string{"rolebindings"},
Verbs: []string{"get", "create", "update"},
},
{
APIGroups: []string{"metrics.k8s.io"},
Resources: []string{"pods", "nodes"},
Verbs: []string{"get", "list", "watch"},
},
{
APIGroups: []string{""},
Resources: []string{"namespaces"},
Verbs: []string{"get", "list"},
},
{
APIGroups: []string{""},
Resources: []string{"events"},
Verbs: []string{"watch", "list"},
},
}
openshiftPolicies := []rbacv1.PolicyRule{
{
APIGroups: []string{"route.openshift.io"},
Resources: []string{"routes"},
Verbs: []string{"get", "list", "create", "delete"},
},
{
APIGroups: []string{"authorization.openshift.io"},
Resources: []string{"roles"},
Verbs: []string{"get", "create", "update"},
},
{
APIGroups: []string{"authorization.openshift.io"},
Resources: []string{"rolebindings"},
Verbs: []string{"get", "create", "update"},
},
{
APIGroups: []string{"project.openshift.io"},
Resources: []string{"projects"},
Verbs: []string{"get"},
},
}
if infrastructure.IsOpenShift() {
return append(k8sPolicies, openshiftPolicies...)
}
return k8sPolicies
}
func (s *CheServerReconciler) getUserClusterRoles(ctx *chetypes.DeployContext) []string {
return []string{
fmt.Sprintf(commonPermissionsTemplateName, ctx.CheCluster.Namespace),
fmt.Sprintf(devWorkspacePermissionsTemplateName, ctx.CheCluster.Namespace),
}
}

View File

@ -0,0 +1,134 @@
//
// 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 server
import (
"context"
"fmt"
"testing"
"github.com/eclipse-che/che-operator/pkg/deploy"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"github.com/devfile/devworkspace-operator/pkg/infrastructure"
chev2 "github.com/eclipse-che/che-operator/api/v2"
"github.com/eclipse-che/che-operator/pkg/common/test"
"github.com/stretchr/testify/assert"
rbac "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/types"
)
func TestSyncPermissions(t *testing.T) {
infrastructure.InitializeForTesting(infrastructure.OpenShiftv4)
type testCase struct {
name string
checluster *chev2.CheCluster
}
testCases := []testCase{
{
name: "Test case #1",
checluster: &chev2.CheCluster{
ObjectMeta: metav1.ObjectMeta{
Name: "eclipse-che",
Namespace: "eclipse-che",
},
Spec: chev2.CheClusterSpec{
Components: chev2.CheClusterComponents{
CheServer: chev2.CheServer{
ClusterRoles: []string{"test-role"},
},
},
},
},
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
ctx := test.GetDeployContext(testCase.checluster, []runtime.Object{})
reconciler := NewCheServerReconciler()
done, err := reconciler.syncPermissions(ctx)
assert.True(t, done)
assert.Nil(t, err)
names := []string{
fmt.Sprintf(commonPermissionsTemplateName, ctx.CheCluster.Namespace),
fmt.Sprintf(namespacePermissionsTemplateName, ctx.CheCluster.Namespace),
fmt.Sprintf(devWorkspacePermissionsTemplateName, ctx.CheCluster.Namespace),
}
for _, name := range names {
assert.True(t, test.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: name}, &rbac.ClusterRole{}))
assert.True(t, test.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: name}, &rbac.ClusterRoleBinding{}))
}
assert.True(t, test.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: "test-role"}, &rbac.ClusterRoleBinding{}))
done = reconciler.deletePermissions(ctx)
assert.True(t, done)
for _, name := range names {
assert.False(t, test.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: name}, &rbac.ClusterRole{}))
assert.False(t, test.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: name}, &rbac.ClusterRoleBinding{}))
}
assert.False(t, test.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: "test-role"}, &rbac.ClusterRoleBinding{}))
})
}
}
// TestSyncClusterRoleBinding tests that CRB is deleted when no roles are specified in CR.
func TestSyncPermissionsWhenCheClusterUpdated(t *testing.T) {
infrastructure.InitializeForTesting(infrastructure.OpenShiftv4)
cheCluster := &chev2.CheCluster{
ObjectMeta: metav1.ObjectMeta{
Name: "eclipse-che",
Namespace: "eclipse-che",
},
Spec: chev2.CheClusterSpec{
Components: chev2.CheClusterComponents{
CheServer: chev2.CheServer{
ClusterRoles: []string{"test-role"},
},
},
},
}
ctx := test.GetDeployContext(cheCluster, []runtime.Object{})
reconciler := NewCheServerReconciler()
done, err := reconciler.syncPermissions(ctx)
assert.True(t, done)
assert.NoError(t, err)
err = deploy.ReloadCheClusterCR(ctx)
assert.NoError(t, err)
assert.True(t, test.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: "test-role"}, &rbac.ClusterRoleBinding{}))
assert.Equal(t, ctx.CheCluster.Finalizers, []string{"test-role.crb.finalizers.che.eclipse.org"})
ctx.CheCluster.Spec.Components.CheServer.ClusterRoles = []string{}
err = ctx.ClusterAPI.Client.Update(context.TODO(), ctx.CheCluster)
assert.NoError(t, err)
done, err = reconciler.syncPermissions(ctx)
assert.True(t, done)
assert.NoError(t, err)
assert.False(t, test.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Namespace: "eclipse-che", Name: "test-role"}, &rbac.ClusterRoleBinding{}))
assert.Empty(t, ctx.CheCluster.Finalizers)
}

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2019-2021 Red Hat, Inc.
// 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/
@ -35,57 +35,42 @@ const (
CheConfigMapName = "che"
)
func addMap(a map[string]string, b map[string]string) {
for k, v := range b {
a[k] = v
}
}
type CheConfigMap struct {
CheHost string `json:"CHE_HOST"`
CheMultiUser string `json:"CHE_MULTIUSER"`
ChePort string `json:"CHE_PORT"`
CheApi string `json:"CHE_API"`
CheApiInternal string `json:"CHE_API_INTERNAL"`
CheWebSocketEndpoint string `json:"CHE_WEBSOCKET_ENDPOINT"`
CheWebSocketInternalEndpoint string `json:"CHE_WEBSOCKET_INTERNAL_ENDPOINT"`
CheDebugServer string `json:"CHE_DEBUG_SERVER"`
CheMetricsEnabled string `json:"CHE_METRICS_ENABLED"`
CheInfrastructureActive string `json:"CHE_INFRASTRUCTURE_ACTIVE"`
CheInfraKubernetesServiceAccountName string `json:"CHE_INFRA_KUBERNETES_SERVICE__ACCOUNT__NAME"`
CheInfraKubernetesUserClusterRoles string `json:"CHE_INFRA_KUBERNETES_USER__CLUSTER__ROLES"`
DefaultTargetNamespace string `json:"CHE_INFRA_KUBERNETES_NAMESPACE_DEFAULT"`
NamespaceCreationAllowed string `json:"CHE_INFRA_KUBERNETES_NAMESPACE_CREATION__ALLOWED"`
PvcStrategy string `json:"CHE_INFRA_KUBERNETES_PVC_STRATEGY"`
PvcClaimSize string `json:"CHE_INFRA_KUBERNETES_PVC_QUANTITY"`
WorkspacePvcStorageClassName string `json:"CHE_INFRA_KUBERNETES_PVC_STORAGE__CLASS__NAME"`
TlsSupport string `json:"CHE_INFRA_OPENSHIFT_TLS__ENABLED"`
K8STrustCerts string `json:"CHE_INFRA_KUBERNETES_TRUST__CERTS"`
CheLogLevel string `json:"CHE_LOG_LEVEL"`
IdentityProviderUrl string `json:"CHE_OIDC_AUTH__SERVER__URL,omitempty"`
IdentityProviderInternalURL string `json:"CHE_OIDC_AUTH__INTERNAL__SERVER__URL,omitempty"`
OpenShiftIdentityProvider string `json:"CHE_INFRA_OPENSHIFT_OAUTH__IDENTITY__PROVIDER"`
JavaOpts string `json:"JAVA_OPTS"`
WorkspaceJavaOpts string `json:"CHE_WORKSPACE_JAVA__OPTIONS"`
WorkspaceMavenOpts string `json:"CHE_WORKSPACE_MAVEN__OPTIONS"`
WorkspaceProxyJavaOpts string `json:"CHE_WORKSPACE_HTTP__PROXY__JAVA__OPTIONS"`
WorkspaceHttpProxy string `json:"CHE_WORKSPACE_HTTP__PROXY"`
WorkspaceHttpsProxy string `json:"CHE_WORKSPACE_HTTPS__PROXY"`
WorkspaceNoProxy string `json:"CHE_WORKSPACE_NO__PROXY"`
PluginRegistryUrl string `json:"CHE_WORKSPACE_PLUGIN__REGISTRY__URL,omitempty"`
PluginRegistryInternalUrl string `json:"CHE_WORKSPACE_PLUGIN__REGISTRY__INTERNAL__URL,omitempty"`
DevfileRegistryUrl string `json:"CHE_WORKSPACE_DEVFILE__REGISTRY__URL,omitempty"`
DevfileRegistryInternalUrl string `json:"CHE_WORKSPACE_DEVFILE__REGISTRY__INTERNAL__URL,omitempty"`
CheWorkspacePluginBrokerMetadataImage string `json:"CHE_WORKSPACE_PLUGIN__BROKER_METADATA_IMAGE,omitempty"`
CheWorkspacePluginBrokerArtifactsImage string `json:"CHE_WORKSPACE_PLUGIN__BROKER_ARTIFACTS_IMAGE,omitempty"`
CheServerSecureExposerJwtProxyImage string `json:"CHE_SERVER_SECURE__EXPOSER_JWTPROXY_IMAGE,omitempty"`
CheJGroupsKubernetesLabels string `json:"KUBERNETES_LABELS,omitempty"`
CheTrustedCABundlesConfigMap string `json:"CHE_TRUSTED__CA__BUNDLES__CONFIGMAP,omitempty"`
ServerStrategy string `json:"CHE_INFRA_KUBERNETES_SERVER__STRATEGY"`
WorkspaceExposure string `json:"CHE_INFRA_KUBERNETES_SINGLEHOST_WORKSPACE_EXPOSURE"`
SingleHostGatewayConfigMapLabels string `json:"CHE_INFRA_KUBERNETES_SINGLEHOST_GATEWAY_CONFIGMAP__LABELS"`
CheDevWorkspacesEnabled string `json:"CHE_DEVWORKSPACES_ENABLED"`
Http2Disable string `json:"HTTP2_DISABLE"`
CheHost string `json:"CHE_HOST"`
CheMultiUser string `json:"CHE_MULTIUSER"`
ChePort string `json:"CHE_PORT"`
CheApi string `json:"CHE_API"`
CheApiInternal string `json:"CHE_API_INTERNAL"`
CheWebSocketEndpoint string `json:"CHE_WEBSOCKET_ENDPOINT"`
CheWebSocketInternalEndpoint string `json:"CHE_WEBSOCKET_INTERNAL_ENDPOINT"`
CheDebugServer string `json:"CHE_DEBUG_SERVER"`
CheMetricsEnabled string `json:"CHE_METRICS_ENABLED"`
CheInfrastructureActive string `json:"CHE_INFRASTRUCTURE_ACTIVE"`
CheInfraKubernetesServiceAccountName string `json:"CHE_INFRA_KUBERNETES_SERVICE__ACCOUNT__NAME"`
CheInfraKubernetesUserClusterRoles string `json:"CHE_INFRA_KUBERNETES_USER__CLUSTER__ROLES"`
DefaultTargetNamespace string `json:"CHE_INFRA_KUBERNETES_NAMESPACE_DEFAULT"`
NamespaceCreationAllowed string `json:"CHE_INFRA_KUBERNETES_NAMESPACE_CREATION__ALLOWED"`
PvcStrategy string `json:"CHE_INFRA_KUBERNETES_PVC_STRATEGY"`
PvcClaimSize string `json:"CHE_INFRA_KUBERNETES_PVC_QUANTITY"`
WorkspacePvcStorageClassName string `json:"CHE_INFRA_KUBERNETES_PVC_STORAGE__CLASS__NAME"`
TlsSupport string `json:"CHE_INFRA_OPENSHIFT_TLS__ENABLED"`
K8STrustCerts string `json:"CHE_INFRA_KUBERNETES_TRUST__CERTS"`
CheLogLevel string `json:"CHE_LOG_LEVEL"`
IdentityProviderUrl string `json:"CHE_OIDC_AUTH__SERVER__URL,omitempty"`
IdentityProviderInternalURL string `json:"CHE_OIDC_AUTH__INTERNAL__SERVER__URL,omitempty"`
OpenShiftIdentityProvider string `json:"CHE_INFRA_OPENSHIFT_OAUTH__IDENTITY__PROVIDER"`
JavaOpts string `json:"JAVA_OPTS"`
PluginRegistryUrl string `json:"CHE_WORKSPACE_PLUGIN__REGISTRY__URL,omitempty"`
PluginRegistryInternalUrl string `json:"CHE_WORKSPACE_PLUGIN__REGISTRY__INTERNAL__URL,omitempty"`
DevfileRegistryUrl string `json:"CHE_WORKSPACE_DEVFILE__REGISTRY__URL,omitempty"`
DevfileRegistryInternalUrl string `json:"CHE_WORKSPACE_DEVFILE__REGISTRY__INTERNAL__URL,omitempty"`
CheJGroupsKubernetesLabels string `json:"KUBERNETES_LABELS,omitempty"`
CheTrustedCABundlesConfigMap string `json:"CHE_TRUSTED__CA__BUNDLES__CONFIGMAP,omitempty"`
ServerStrategy string `json:"CHE_INFRA_KUBERNETES_SERVER__STRATEGY"`
WorkspaceExposure string `json:"CHE_INFRA_KUBERNETES_SINGLEHOST_WORKSPACE_EXPOSURE"`
SingleHostGatewayConfigMapLabels string `json:"CHE_INFRA_KUBERNETES_SINGLEHOST_GATEWAY_CONFIGMAP__LABELS"`
CheDevWorkspacesEnabled string `json:"CHE_DEVWORKSPACES_ENABLED"`
Http2Disable string `json:"HTTP2_DISABLE"`
}
// GetCheConfigMapData gets env values from CR spec and returns a map with key:value
@ -187,47 +172,36 @@ func (s *CheServerReconciler) getCheConfigMapData(ctx *chetypes.DeployContext) (
webSocketInternalEndpoint := fmt.Sprintf("ws://%s.%s.svc:8080/api/websocket", deploy.CheServiceName, ctx.CheCluster.Namespace)
webSocketEndpoint := "wss://" + ctx.CheHost + "/api/websocket"
cheWorkspaceServiceAccount := "NULL"
cheUserClusterRoleNames := fmt.Sprintf("%s-cheworkspaces-clusterrole, %s-cheworkspaces-devworkspace-clusterrole", ctx.CheCluster.Namespace, ctx.CheCluster.Namespace)
data := &CheConfigMap{
CheMultiUser: "true",
CheHost: ctx.CheHost,
ChePort: "8080",
CheApi: cheAPI,
CheApiInternal: cheInternalAPI,
CheWebSocketEndpoint: webSocketEndpoint,
CheWebSocketInternalEndpoint: webSocketInternalEndpoint,
CheDebugServer: cheDebug,
CheInfrastructureActive: infra,
CheInfraKubernetesServiceAccountName: cheWorkspaceServiceAccount,
CheInfraKubernetesUserClusterRoles: cheUserClusterRoleNames,
DefaultTargetNamespace: workspaceNamespaceDefault,
NamespaceCreationAllowed: namespaceCreationAllowed,
TlsSupport: "true",
K8STrustCerts: "true",
CheLogLevel: cheLogLevel,
OpenShiftIdentityProvider: openShiftIdentityProviderId,
JavaOpts: constants.DefaultJavaOpts + " " + proxyJavaOpts,
WorkspaceJavaOpts: constants.DefaultWorkspaceJavaOpts + " " + proxyJavaOpts,
WorkspaceMavenOpts: constants.DefaultWorkspaceJavaOpts + " " + proxyJavaOpts,
WorkspaceProxyJavaOpts: proxyJavaOpts,
WorkspaceHttpProxy: ctx.Proxy.HttpProxy,
WorkspaceHttpsProxy: ctx.Proxy.HttpsProxy,
WorkspaceNoProxy: cheWorkspaceNoProxy,
PluginRegistryUrl: pluginRegistryURL,
PluginRegistryInternalUrl: pluginRegistryInternalURL,
DevfileRegistryUrl: devfileRegistryURL,
DevfileRegistryInternalUrl: devfileRegistryInternalURL,
CheWorkspacePluginBrokerMetadataImage: defaults.GetCheWorkspacePluginBrokerMetadataImage(ctx.CheCluster),
CheWorkspacePluginBrokerArtifactsImage: defaults.GetCheWorkspacePluginBrokerArtifactsImage(ctx.CheCluster),
CheServerSecureExposerJwtProxyImage: defaults.GetCheServerSecureExposerJwtProxyImage(ctx.CheCluster),
CheJGroupsKubernetesLabels: cheLabels,
CheMetricsEnabled: cheMetrics,
CheTrustedCABundlesConfigMap: deploytls.CheAllCACertsConfigMapName,
ServerStrategy: "single-host",
WorkspaceExposure: "gateway",
SingleHostGatewayConfigMapLabels: singleHostGatewayConfigMapLabels,
CheDevWorkspacesEnabled: strconv.FormatBool(true),
CheMultiUser: "true",
CheHost: ctx.CheHost,
ChePort: "8080",
CheApi: cheAPI,
CheApiInternal: cheInternalAPI,
CheWebSocketEndpoint: webSocketEndpoint,
CheWebSocketInternalEndpoint: webSocketInternalEndpoint,
CheDebugServer: cheDebug,
CheInfrastructureActive: infra,
CheInfraKubernetesServiceAccountName: cheWorkspaceServiceAccount,
DefaultTargetNamespace: workspaceNamespaceDefault,
NamespaceCreationAllowed: namespaceCreationAllowed,
TlsSupport: "true",
K8STrustCerts: "true",
CheLogLevel: cheLogLevel,
OpenShiftIdentityProvider: openShiftIdentityProviderId,
JavaOpts: constants.DefaultJavaOpts + " " + proxyJavaOpts,
PluginRegistryUrl: pluginRegistryURL,
PluginRegistryInternalUrl: pluginRegistryInternalURL,
DevfileRegistryUrl: devfileRegistryURL,
DevfileRegistryInternalUrl: devfileRegistryInternalURL,
CheJGroupsKubernetesLabels: cheLabels,
CheMetricsEnabled: cheMetrics,
CheTrustedCABundlesConfigMap: deploytls.CheAllCACertsConfigMapName,
ServerStrategy: "single-host",
WorkspaceExposure: "gateway",
SingleHostGatewayConfigMapLabels: singleHostGatewayConfigMapLabels,
CheDevWorkspacesEnabled: strconv.FormatBool(true),
// Disable HTTP2 protocol.
// Fix issue with creating config maps on the cluster https://issues.redhat.com/browse/CRW-2677
// The root cause is in the HTTP2 protocol support of the okttp3 library that is used by fabric8.kubernetes-client that is used by che-server
@ -255,7 +229,7 @@ func (s *CheServerReconciler) getCheConfigMapData(ctx *chetypes.DeployContext) (
"CHE_INFRA_KUBERNETES_INGRESS_PATH__TRANSFORM": "%s(.*)",
}
k8sCheEnv["CHE_INFRA_KUBERNETES_ENABLE__UNSUPPORTED__K8S"] = "true"
addMap(cheEnv, k8sCheEnv)
utils.AddMap(cheEnv, k8sCheEnv)
}
// Add TLS key and server certificate to properties since user workspaces is created in another
@ -280,10 +254,12 @@ func (s *CheServerReconciler) getCheConfigMapData(ctx *chetypes.DeployContext) (
}
}
addMap(cheEnv, ctx.CheCluster.Spec.Components.CheServer.ExtraProperties)
utils.AddMap(cheEnv, ctx.CheCluster.Spec.Components.CheServer.ExtraProperties)
s.updateUserClusterRoles(ctx, cheEnv)
for _, oauthProvider := range []string{"bitbucket", "gitlab", "github", constants.AzureDevOpsOAuth} {
err := updateIntegrationServerEndpoints(ctx, cheEnv, oauthProvider)
err := s.updateIntegrationServerEndpoints(ctx, cheEnv, oauthProvider)
if err != nil {
return nil, err
}
@ -292,7 +268,7 @@ func (s *CheServerReconciler) getCheConfigMapData(ctx *chetypes.DeployContext) (
return cheEnv, nil
}
func updateIntegrationServerEndpoints(ctx *chetypes.DeployContext, cheEnv map[string]string, oauthProvider string) error {
func (s *CheServerReconciler) updateIntegrationServerEndpoints(ctx *chetypes.DeployContext, cheEnv map[string]string, oauthProvider string) error {
secret, err := getOAuthConfig(ctx, oauthProvider)
if secret == nil {
return err
@ -319,3 +295,25 @@ func GetCheConfigMapVersion(deployContext *chetypes.DeployContext) string {
}
return ""
}
func (s *CheServerReconciler) updateUserClusterRoles(ctx *chetypes.DeployContext, cheEnv map[string]string) {
userClusterRoles := strings.Join(s.getUserClusterRoles(ctx), ", ")
for _, role := range strings.Split(cheEnv["CHE_INFRA_KUBERNETES_USER__CLUSTER__ROLES"], ",") {
role := strings.TrimSpace(role)
if !strings.Contains(userClusterRoles, role) {
userClusterRoles = userClusterRoles + ", " + role
}
}
if ctx.CheCluster.Spec.DevEnvironments.User != nil {
for _, role := range ctx.CheCluster.Spec.DevEnvironments.User.ClusterRoles {
role := strings.TrimSpace(role)
if !strings.Contains(userClusterRoles, role) {
userClusterRoles = userClusterRoles + ", " + role
}
}
}
cheEnv["CHE_INFRA_KUBERNETES_USER__CLUSTER__ROLES"] = userClusterRoles
}

View File

@ -493,3 +493,81 @@ func TestShouldSetUpCorrectlyInternalCheServerURL(t *testing.T) {
})
}
}
func TestUpdateUserClusterRoles(t *testing.T) {
type testCase struct {
name string
cheCluster *chev2.CheCluster
expectedUserClusterRoles string
}
testCases := []testCase{
{
name: "Test #1",
cheCluster: &chev2.CheCluster{
ObjectMeta: metav1.ObjectMeta{
Name: "eclipse-che",
Namespace: "eclipse-che",
},
},
expectedUserClusterRoles: "eclipse-che-cheworkspaces-clusterrole, eclipse-che-cheworkspaces-devworkspace-clusterrole",
},
{
name: "Test #2",
cheCluster: &chev2.CheCluster{
ObjectMeta: metav1.ObjectMeta{
Name: "eclipse-che",
Namespace: "eclipse-che",
},
Spec: chev2.CheClusterSpec{
Components: chev2.CheClusterComponents{
CheServer: chev2.CheServer{
ExtraProperties: map[string]string{
"CHE_INFRA_KUBERNETES_USER__CLUSTER__ROLES": "eclipse-che-cheworkspaces-clusterrole, test-roles-1, test-roles-2",
},
},
},
},
},
expectedUserClusterRoles: "eclipse-che-cheworkspaces-clusterrole, eclipse-che-cheworkspaces-devworkspace-clusterrole, test-roles-1, test-roles-2",
},
{
name: "Test #3",
cheCluster: &chev2.CheCluster{
ObjectMeta: metav1.ObjectMeta{
Name: "eclipse-che",
Namespace: "eclipse-che",
},
Spec: chev2.CheClusterSpec{
Components: chev2.CheClusterComponents{
CheServer: chev2.CheServer{
ExtraProperties: map[string]string{
"CHE_INFRA_KUBERNETES_USER__CLUSTER__ROLES": "eclipse-che-cheworkspaces-clusterrole, test-roles-1, test-roles-2",
},
},
},
DevEnvironments: chev2.CheClusterDevEnvironments{
User: &chev2.UserConfiguration{
ClusterRoles: []string{
"test-roles-3",
},
},
},
},
},
expectedUserClusterRoles: "eclipse-che-cheworkspaces-clusterrole, eclipse-che-cheworkspaces-devworkspace-clusterrole, test-roles-1, test-roles-2, test-roles-3",
},
}
for _, testCase := range testCases {
t.Run(testCase.name, func(t *testing.T) {
ctx := test.GetDeployContext(testCase.cheCluster, []runtime.Object{})
reconciler := NewCheServerReconciler()
cheEnv, err := reconciler.getCheConfigMapData(ctx)
assert.NoError(t, err)
assert.Equal(t, testCase.expectedUserClusterRoles, cheEnv["CHE_INFRA_KUBERNETES_USER__CLUSTER__ROLES"])
})
}
}

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2019-2021 Red Hat, Inc.
// 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/
@ -14,6 +14,7 @@ package server
import (
chev2 "github.com/eclipse-che/che-operator/api/v2"
"github.com/eclipse-che/che-operator/pkg/common/chetypes"
"github.com/eclipse-che/che-operator/pkg/common/constants"
defaults "github.com/eclipse-che/che-operator/pkg/common/operator-defaults"
"github.com/eclipse-che/che-operator/pkg/deploy"
"github.com/sirupsen/logrus"
@ -22,6 +23,11 @@ import (
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
const (
crb = ".crb."
cheCRBFinalizerSuffix = crb + constants.FinalizerSuffix
)
type CheServerReconciler struct {
deploy.Reconcilable
}
@ -43,6 +49,14 @@ func (s *CheServerReconciler) Reconcile(ctx *chetypes.DeployContext) (reconcile.
return reconcile.Result{}, false, err
}
if done, err := deploy.SyncServiceAccountToCluster(ctx, constants.DefaultCheServiceAccountName); !done {
return reconcile.Result{}, false, err
}
if done, err := s.syncPermissions(ctx); !done {
return reconcile.Result{}, false, err
}
done, err = s.syncDeployment(ctx)
if !done {
return reconcile.Result{}, false, err
@ -66,8 +80,8 @@ func (s *CheServerReconciler) Reconcile(ctx *chetypes.DeployContext) (reconcile.
return reconcile.Result{}, true, nil
}
func (s *CheServerReconciler) Finalize(ctx *chetypes.DeployContext) bool {
return true
func (c *CheServerReconciler) Finalize(ctx *chetypes.DeployContext) bool {
return c.deletePermissions(ctx)
}
func (s *CheServerReconciler) syncCheConfigMap(ctx *chetypes.DeployContext) (bool, error) {
@ -115,6 +129,15 @@ func (s *CheServerReconciler) syncActiveChePhase(ctx *chetypes.DeployContext) (b
return true, nil
}
func (s *CheServerReconciler) getCRBFinalizerName(crbName string) string {
finalizer := crbName + cheCRBFinalizerSuffix
diff := len(finalizer) - 63
if diff > 0 {
return finalizer[:len(finalizer)-diff]
}
return finalizer
}
func (s *CheServerReconciler) syncDeployment(ctx *chetypes.DeployContext) (bool, error) {
spec, err := s.getDeploymentSpec(ctx)
if err != nil {
@ -133,6 +156,7 @@ func (s CheServerReconciler) syncCheVersion(ctx *chetypes.DeployContext) (bool,
}
return true, nil
}
func (s CheServerReconciler) syncCheURL(ctx *chetypes.DeployContext) (bool, error) {
var cheUrl = "https://" + ctx.CheHost
if ctx.CheCluster.Status.CheURL != cheUrl {

View File

@ -1,5 +1,5 @@
//
// Copyright (c) 2019-2021 Red Hat, Inc.
// 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/
@ -15,13 +15,13 @@ import (
"context"
"github.com/devfile/devworkspace-operator/pkg/infrastructure"
chev2 "github.com/eclipse-che/che-operator/api/v2"
defaults "github.com/eclipse-che/che-operator/pkg/common/operator-defaults"
"github.com/eclipse-che/che-operator/pkg/common/test"
"github.com/stretchr/testify/assert"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
chev2 "github.com/eclipse-che/che-operator/api/v2"
rbac "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
@ -37,6 +37,13 @@ func TestReconcile(t *testing.T) {
Namespace: "eclipse-che",
Name: "eclipse-che",
},
Spec: chev2.CheClusterSpec{
Components: chev2.CheClusterComponents{
CheServer: chev2.CheServer{
ClusterRoles: []string{"test-role"},
},
},
},
}
ctx := test.GetDeployContext(cheCluster, []runtime.Object{})
@ -47,8 +54,11 @@ func TestReconcile(t *testing.T) {
assert.Nil(t, err)
assert.True(t, test.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: CheConfigMapName, Namespace: "eclipse-che"}, &corev1.ConfigMap{}))
assert.True(t, test.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Namespace: "eclipse-che", Name: "che"}, &corev1.ServiceAccount{}))
assert.True(t, test.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: "test-role"}, &rbac.ClusterRoleBinding{}))
assert.True(t, test.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: getComponentName(ctx), Namespace: "eclipse-che"}, &appsv1.Deployment{}))
assert.Equal(t, ctx.CheCluster.Status.ChePhase, chev2.CheClusterPhase(chev2.ClusterPhaseInactive))
assert.Equal(t, 1, len(ctx.CheCluster.Finalizers))
cheDeployment := &appsv1.Deployment{}
err = ctx.ClusterAPI.Client.Get(context.TODO(), types.NamespacedName{Name: defaults.GetCheFlavor(), Namespace: "eclipse-che"}, cheDeployment)
@ -63,8 +73,13 @@ func TestReconcile(t *testing.T) {
assert.Nil(t, err)
assert.Equal(t, ctx.CheCluster.Status.ChePhase, chev2.CheClusterPhase(chev2.ClusterPhaseActive))
assert.NotEmpty(t, cheCluster.Status.CheVersion)
assert.NotEmpty(t, cheCluster.Status.CheURL)
assert.NotEmpty(t, ctx.CheCluster.Status.CheVersion)
assert.NotEmpty(t, ctx.CheCluster.Status.CheURL)
done = server.Finalize(ctx)
assert.True(t, done)
assert.False(t, test.IsObjectExists(ctx.ClusterAPI.Client, types.NamespacedName{Name: "test-role"}, &rbac.ClusterRoleBinding{}))
}
func TestUpdateAvailabilityStatus(t *testing.T) {
@ -103,3 +118,13 @@ func TestUpdateAvailabilityStatus(t *testing.T) {
assert.Nil(t, err)
assert.Equal(t, ctx.CheCluster.Status.ChePhase, chev2.CheClusterPhase(chev2.RollingUpdate))
}
func TestGetFinalizerName(t *testing.T) {
crbName := "0123456789012345678901234567890123456789" // 40 chars
reconciler := NewCheServerReconciler()
finalizer := reconciler.getCRBFinalizerName(crbName)
assert.Equal(t, crbName+".crb.finalizers.che.ecl", finalizer)
assert.True(t, len(finalizer) <= 63)
}

View File

@ -62,25 +62,6 @@ func SyncWithClient(cli client.Client, deployContext *chetypes.DeployContext, bl
return UpdateWithClient(cli, deployContext, actual.(client.Object), blueprint, diffOpts...)
}
func SyncAndAddFinalizer(
deployContext *chetypes.DeployContext,
blueprint metav1.Object,
diffOpts cmp.Option,
finalizer string) (bool, error) {
// eclipse-che custom resource is being deleted, we shouldn't sync
// TODO move this check before `Sync` invocation
if deployContext.CheCluster.ObjectMeta.DeletionTimestamp.IsZero() {
done, err := Sync(deployContext, blueprint.(client.Object), diffOpts)
if !done {
return done, err
}
err = AppendFinalizer(deployContext, finalizer)
return err == nil, err
}
return true, nil
}
// Gets object by key.
// Returns true if object exists otherwise returns false.
func Get(deployContext *chetypes.DeployContext, key client.ObjectKey, actual client.Object) (bool, error) {

View File

@ -16,7 +16,6 @@ import (
chev2 "github.com/eclipse-che/che-operator/api/v2"
"github.com/eclipse-che/che-operator/pkg/common/chetypes"
"github.com/eclipse-che/che-operator/pkg/common/utils"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
corev1 "k8s.io/api/core/v1"
@ -171,28 +170,6 @@ func TestUpdate(t *testing.T) {
}
}
func TestSyncAndAddFinalizer(t *testing.T) {
cli, deployContext := initDeployContext()
cli.Create(context.TODO(), deployContext.CheCluster)
// Sync object
done, err := SyncAndAddFinalizer(deployContext, testObj.DeepCopy(), cmp.Options{}, "test-finalizer")
if !done || err != nil {
t.Fatalf("Error syncing object: %v", err)
}
actual := &corev1.Secret{}
err = cli.Get(context.TODO(), testKey, actual)
if err != nil {
t.Fatalf("Failed to get object: %v", err)
}
if !utils.Contains(deployContext.CheCluster.Finalizers, "test-finalizer") {
t.Fatalf("Failed to add finalizer")
}
}
func TestShouldDeleteExistedObject(t *testing.T) {
cli, deployContext := initDeployContext()